Revert "Merging from ub-launcher3-master @ build 6294827"

Revert "Merging from ub-launcher3-master @ build 6294827"

Revert "Merging from ub-launcher3-master @ build 6294827"

Revert "Merging from ub-launcher3-master @ build 6294827"

Revert "Merging from ub-launcher3-master @ build 6294827"

Revert submission 10673936-merge_ub-launcher3-master_6294827

Reason for revert: b/151611270
Reverted Changes:
I38a587a1b:Merging from ub-launcher3-master @ build 6294827
I74ae8bea8:Merging from ub-launcher3-master @ build 6294827
I115742e03:Merging from ub-launcher3-master @ build 6294827
Iceb1e8523:Merging from ub-launcher3-master @ build 6294827
Ie242e3907:Merging from ub-launcher3-master @ build 6294827
I609b18fdd:Merging from ub-launcher3-master @ build 6294827

Change-Id: I96f08492cb92e2f670375269423d8b45a81312cf
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 814b728..217a41c 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -51,10 +52,8 @@
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 
-/**
- * Launcher BaseActivity
- */
-public abstract class BaseActivity extends Activity implements LogStateProvider, ActivityContext {
+public abstract class BaseActivity extends Activity
+        implements UserEventDelegate, LogStateProvider, ActivityContext {
 
     private static final String TAG = "BaseActivity";
 
@@ -156,7 +155,7 @@
 
     public final UserEventDispatcher getUserEventDispatcher() {
         if (mUserEventDispatcher == null) {
-            mUserEventDispatcher = UserEventDispatcher.newInstance(this);
+            mUserEventDispatcher = UserEventDispatcher.newInstance(this, this);
         }
         return mUserEventDispatcher;
     }
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 38e1201..864fa6e 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,11 +22,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 
 /**
  * A base {@link RecyclerView}, which does the following:
@@ -138,7 +138,7 @@
         if (getCurrentScrollY() == 0) {
             return true;
         }
-        return getAdapter() == null || getAdapter().getItemCount() == 0;
+        return false;
     }
 
     /**
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 76cfe1c..b89e727 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,13 +16,12 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -33,8 +32,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.Transposable;
 
-import java.util.ArrayList;
-
 public class Hotseat extends CellLayout implements LogContainerProvider, Insettable, Transposable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -78,12 +75,10 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        child.rank = childInfo.rank;
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        parents.add(newContainerTarget(LauncherLogProto.ContainerType.HOTSEAT));
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        targetParent.containerType = LauncherLogProto.ContainerType.HOTSEAT;
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1413a5c..965b87b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -77,7 +78,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -110,6 +110,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelWriter;
@@ -124,8 +125,8 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -182,7 +183,8 @@
  * Default launcher application.
  */
 public class Launcher extends BaseDraggingActivity implements LauncherExterns,
-        Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
+        Callbacks, UserEventDelegate,
+        InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
     public static final String TAG = "Launcher";
 
     public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
@@ -326,19 +328,6 @@
     private boolean mDeferOverlayCallbacks;
     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
 
-    private BackgroundBlurController mBackgroundBlurController =
-            new BackgroundBlurController(this);
-
-    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
-            new ViewTreeObserver.OnDrawListener() {
-                @Override
-                public void onDraw() {
-                    getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
-                    mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
-                            this));
-                }
-            };
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
@@ -940,8 +929,6 @@
         final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
 
-        mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
-
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -954,7 +941,6 @@
 
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
-        getBackgroundBlurController().setSurfaceToLauncher(null);
 
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
@@ -982,7 +968,6 @@
         if (!mDeferOverlayCallbacks) {
             mOverlayManager.onActivityStarted(this);
         }
-        mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
@@ -1905,6 +1890,24 @@
     }
 
     @Override
+    public void modifyUserEvent(LauncherLogProto.LauncherEvent event) {
+        if (event.srcTarget != null && event.srcTarget.length > 0 &&
+                event.srcTarget[1].containerType == ContainerType.PREDICTION) {
+            Target[] targets = new Target[3];
+            targets[0] = event.srcTarget[0];
+            targets[1] = event.srcTarget[1];
+            targets[2] = newTarget(Target.Type.CONTAINER);
+            event.srcTarget = targets;
+            LauncherState state = mStateManager.getState();
+            if (state == LauncherState.ALL_APPS) {
+                event.srcTarget[2].containerType = ContainerType.ALLAPPS;
+            } else if (state == OVERVIEW) {
+                event.srcTarget[2].containerType = ContainerType.TASKSWITCHER;
+            }
+        }
+    }
+
+    @Override
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
             @Nullable String sourceContainer) {
         if (!hasBeenResumed()) {
@@ -2707,8 +2710,7 @@
     }
 
     protected StateHandler[] createStateHandlers() {
-        return new StateHandler[] { getAllAppsController(), getWorkspace(),
-                getBackgroundBlurController() };
+        return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
     public TouchController[] createTouchControllers() {
@@ -2745,12 +2747,8 @@
         return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
-    public BackgroundBlurController getBackgroundBlurController() {
-        return mBackgroundBlurController;
-    }
-
     public static Launcher getLauncher(Context context) {
-        return fromContext(context);
+        return (Launcher) fromContext(context);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index d9cf7f1..32e9c14 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -18,7 +18,7 @@
 
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
-import android.util.IntProperty;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 
@@ -32,15 +32,15 @@
     // The progress of an animation to all apps must be at least this far along to snap to all apps.
     public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
 
-    public static final IntProperty<Drawable> DRAWABLE_ALPHA =
-            new IntProperty<Drawable>("drawableAlpha") {
+    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
+            new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") {
                 @Override
                 public Integer get(Drawable drawable) {
                     return drawable.getAlpha();
                 }
 
                 @Override
-                public void setValue(Drawable drawable, int alpha) {
+                public void set(Drawable drawable, Integer alpha) {
                     drawable.setAlpha(alpha);
                 }
             };
@@ -64,28 +64,28 @@
         return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
     }
 
-    public static final IntProperty<LayoutParams> LAYOUT_WIDTH =
-            new IntProperty<LayoutParams>("width") {
+    public static final Property<LayoutParams, Integer> LAYOUT_WIDTH =
+            new Property<LayoutParams, Integer>(Integer.TYPE, "width") {
                 @Override
                 public Integer get(LayoutParams lp) {
                     return lp.width;
                 }
 
                 @Override
-                public void setValue(LayoutParams lp, int width) {
+                public void set(LayoutParams lp, Integer width) {
                     lp.width = width;
                 }
             };
 
-    public static final IntProperty<LayoutParams> LAYOUT_HEIGHT =
-            new IntProperty<LayoutParams>("height") {
+    public static final Property<LayoutParams, Integer> LAYOUT_HEIGHT =
+            new Property<LayoutParams, Integer>(Integer.TYPE, "height") {
                 @Override
                 public Integer get(LayoutParams lp) {
                     return lp.height;
                 }
 
                 @Override
-                public void setValue(LayoutParams lp, int height) {
+                public void set(LayoutParams lp, Integer height) {
                     lp.height = height;
                 }
             };
@@ -117,18 +117,4 @@
                             return view.getTranslationY();
                         }
                     };
-
-    public static final FloatProperty<View> VIEW_ALPHA =
-            View.ALPHA instanceof FloatProperty ? (FloatProperty) View.ALPHA
-                    : new FloatProperty<View>("alpha") {
-                        @Override
-                        public void setValue(View view, float v) {
-                            view.setAlpha(v);
-                        }
-
-                        @Override
-                        public Float get(View view) {
-                            return view.getAlpha();
-                        }
-                    };
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 62b8927..36440c9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -41,7 +41,6 @@
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
-import android.content.Context;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -270,14 +269,6 @@
         return 0;
     }
 
-    /**
-     * The amount of blur to apply to the background of either the app or Launcher surface in this
-     * state.
-     */
-    public int getBackgroundBlurRadius(Context context) {
-        return 0;
-    }
-
     public String getDescription(Launcher launcher) {
         return launcher.getWorkspace().getCurrentPageDescription();
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 04134f2..9f25729 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -88,22 +88,20 @@
     // components may be run atomically - that is, all at once, instead of user-controlled. However,
     // atomic components are not restricted to this purpose; they can be user-controlled alongside
     // non atomic components as well. Note that each gesture model has exactly one atomic component,
-    // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
+    // ATOMIC_OVERVIEW_SCALE_COMPONENT *or* ATOMIC_OVERVIEW_PEEK_COMPONENT.
     @IntDef(flag = true, value = {
-            PLAY_NON_ATOMIC,
-            PLAY_ATOMIC_OVERVIEW_SCALE,
-            PLAY_ATOMIC_OVERVIEW_PEEK,
-            SKIP_OVERVIEW,
+            NON_ATOMIC_COMPONENT,
+            ATOMIC_OVERVIEW_SCALE_COMPONENT,
+            ATOMIC_OVERVIEW_PEEK_COMPONENT,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface AnimationFlags {}
-    public static final int PLAY_NON_ATOMIC = 1 << 0;
-    public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
-    public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
-    public static final int SKIP_OVERVIEW = 1 << 3;
+    public @interface AnimationComponents {}
+    public static final int NON_ATOMIC_COMPONENT = 1 << 0;
+    public static final int ATOMIC_OVERVIEW_SCALE_COMPONENT = 1 << 1;
+    public static final int ATOMIC_OVERVIEW_PEEK_COMPONENT = 1 << 2;
 
-    public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
-            | PLAY_ATOMIC_OVERVIEW_PEEK;
+    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_OVERVIEW_SCALE_COMPONENT
+            | ATOMIC_OVERVIEW_PEEK_COMPONENT;
 
     private final AnimationConfig mConfig = new AnimationConfig();
     private final Handler mUiHandler;
@@ -246,8 +244,12 @@
             } else if (!mConfig.userControlled && animated && mConfig.mTargetState == state) {
                 // We are running the same animation as requested
                 if (onCompleteRunnable != null) {
-                    mConfig.mCurrentAnimation.addListener(
-                            AnimationSuccessListener.forRunnable(onCompleteRunnable));
+                    mConfig.mCurrentAnimation.addListener(new AnimationSuccessListener() {
+                        @Override
+                        public void onAnimationSuccess(Animator animator) {
+                            onCompleteRunnable.run();
+                        }
+                    });
                 }
                 return;
             }
@@ -313,10 +315,10 @@
     }
 
     public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
-            AnimatorSetBuilder builder, @AnimationFlags int animFlags, long duration) {
+            AnimatorSetBuilder builder, @AnimationComponents int atomicComponent, long duration) {
         prepareForAtomicAnimation(fromState, toState, builder);
         AnimationConfig config = new AnimationConfig();
-        config.mAnimFlags = animFlags;
+        config.animComponents = atomicComponent;
         config.duration = duration;
         for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
             handler.setStateWithAnimation(toState, builder, config);
@@ -357,25 +359,25 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
-        return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
+        return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState state, long duration, @AnimationFlags int animComponents) {
+            LauncherState state, long duration, @AnimationComponents int animComponents) {
         return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
                 animComponents);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
             AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
-            @AnimationFlags int animComponents) {
+            @AnimationComponents int animComponents) {
         mConfig.reset();
         mConfig.userControlled = true;
-        mConfig.mAnimFlags = animComponents;
+        mConfig.animComponents = animComponents;
         mConfig.duration = duration;
         mConfig.playbackController = AnimatorPlaybackController.wrap(
-                createAnimationToNewWorkspaceInternal(state, builder, null), duration)
-                .setOnCancelRunnable(onCancelRunnable);
+                createAnimationToNewWorkspaceInternal(state, builder, null), duration,
+                onCancelRunnable);
         return mConfig.playbackController;
     }
 
@@ -585,7 +587,7 @@
         public long duration;
         public boolean userControlled;
         public AnimatorPlaybackController playbackController;
-        private @AnimationFlags int mAnimFlags = ANIM_ALL_COMPONENTS;
+        public @AnimationComponents int animComponents = ANIM_ALL;
         private PropertySetter mPropertySetter;
 
         private AnimatorSet mCurrentAnimation;
@@ -599,7 +601,7 @@
         public void reset() {
             duration = 0;
             userControlled = false;
-            mAnimFlags = ANIM_ALL_COMPONENTS;
+            animComponents = ANIM_ALL;
             mPropertySetter = null;
             mTargetState = null;
 
@@ -640,39 +642,16 @@
             mCurrentAnimation.addListener(this);
         }
 
-        /**
-         * @return Whether Overview is scaling as part of this animation. If this is the only
-         * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
-         * atomically, rather than being part of a normal state animation. StateHandlers can use
-         * this to designate part of their animation that should scale with Overview.
-         */
         public boolean playAtomicOverviewScaleComponent() {
-            return hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_SCALE);
+            return (animComponents & ATOMIC_OVERVIEW_SCALE_COMPONENT) != 0;
         }
 
-        /**
-         * @return Whether this animation will play atomically at the same time as a different,
-         * user-controlled state transition. StateHandlers, which contribute to both animations, can
-         * use this to avoid animating the same properties in both animations, since they'd conflict
-         * with one another.
-         */
-        public boolean onlyPlayAtomicComponent() {
-            return getAnimComponents() == PLAY_ATOMIC_OVERVIEW_SCALE
-                    || getAnimComponents() == PLAY_ATOMIC_OVERVIEW_PEEK;
+        public boolean playAtomicOverviewPeekComponent() {
+            return (animComponents & ATOMIC_OVERVIEW_PEEK_COMPONENT) != 0;
         }
 
-        /**
-         * Returns true if the config and any of the provided component flags
-         */
-        public boolean hasAnimationFlag(@AnimationFlags int a) {
-            return (mAnimFlags & a) != 0;
-        }
-
-        /**
-         * @return Only the flags that determine which animation components to play.
-         */
-        public @AnimationFlags int getAnimComponents() {
-            return mAnimFlags & ANIM_ALL_COMPONENTS;
+        public boolean playNonAtomicComponent() {
+            return (animComponents & NON_ATOMIC_COMPONENT) != 0;
         }
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e38631d..a6180a6 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -42,6 +42,21 @@
 import android.view.animation.Interpolator;
 import android.widget.ScrollView;
 
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
+import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -50,20 +65,6 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.util.OverScroller;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
@@ -72,6 +73,8 @@
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
 
+    public static boolean sFlagForcedRotation = false;
+
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
@@ -197,6 +200,8 @@
         if (Utilities.ATLEAST_OREO) {
             setDefaultFocusHighlightEnabled(false);
         }
+
+        sFlagForcedRotation = Utilities.isForcedRotation(context);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -1510,7 +1515,7 @@
         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 
-        if (QUICKSTEP_SPRINGS.get() && mCurrentPage != whichPage) {
+        if (QUICKSTEP_SPRINGS.get()) {
             return snapToPage(whichPage, delta, duration, false, null,
                     velocity * Math.signum(delta), true);
         } else {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 2430d5e..1841134 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.util.Themes;
 
 import java.net.URISyntaxException;
-import java.util.ArrayList;
 
 /**
  * Drop target which provides a secondary option for an item.
@@ -323,9 +322,9 @@
         }
 
         @Override
-        public void fillInLogContainerData(ItemInfo childInfo, Target child,
-                ArrayList<Target> parents) {
-            mOriginal.fillInLogContainerData(childInfo, child, parents);
+        public void fillInLogContainerData(View v, ItemInfo info, Target target,
+                Target targetParent) {
+            mOriginal.fillInLogContainerData(v, info, target, targetParent);
         }
 
         @Override
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 122b393..9780630 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -60,7 +61,6 @@
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -128,6 +128,11 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
+    public static boolean isForcedRotation(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+            FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0;
+    }
+
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -476,15 +481,6 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
-    /**
-     * @return {@link SharedPreferences} that backs {@link FeatureFlags}
-     */
-    public static SharedPreferences getFeatureFlagsPrefs(Context context) {
-        // Use application context for shared preferences, so that we use a single cached instance
-        return context.getApplicationContext().getSharedPreferences(
-            FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
-    }
-
     public static boolean areAnimationsEnabled(Context context) {
         return ATLEAST_OREO
                 ? ValueAnimator.areAnimatorsEnabled()
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc1a074..590c620 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -3310,21 +3309,17 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
-                || childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            getHotseat().fillInLogContainerData(childInfo, child, parents);
-            return;
-        } else if (childInfo.container >= 0) {
-            FolderIcon icon = (FolderIcon) getHomescreenIconByItemId(childInfo.container);
-            icon.getFolder().fillInLogContainerData(childInfo, child, parents);
-            return;
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        target.pageIndex = getCurrentPage();
+        targetParent.containerType = ContainerType.WORKSPACE;
+        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            target.rank = info.rank;
+            targetParent.containerType = ContainerType.HOTSEAT;
+        } else if (info.container >= 0) {
+            targetParent.containerType = ContainerType.FOLDER;
         }
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = getCurrentPage();
-        parents.add(newContainerTarget(ContainerType.WORKSPACE));
     }
 
     /**
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 6653426..c33392d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,9 +18,6 @@
 
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
@@ -120,7 +117,7 @@
                     hotseatIconsAlpha, fadeInterpolator);
         }
 
-        if (config.onlyPlayAtomicComponent()) {
+        if (!config.playNonAtomicComponent()) {
             // Only the alpha and scale, handled above, are included in the atomic animation.
             return;
         }
@@ -128,18 +125,18 @@
         Interpolator translationInterpolator = !playAtomicComponent
                 ? LINEAR
                 : builder.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
-        propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_X,
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
                 scaleAndTranslation.translationX, translationInterpolator);
-        propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_Y,
+        propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation.translationY, translationInterpolator);
 
         Interpolator hotseatTranslationInterpolator = builder.getInterpolator(
                 ANIM_HOTSEAT_TRANSLATE, translationInterpolator);
-        propertySetter.setFloat(hotseat, VIEW_TRANSLATE_Y,
+        propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
-        propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
+        propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
-        propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
+        propertySetter.setFloat(qsbView, View.TRANSLATION_Y,
                 qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
 
         setScrim(propertySetter, state);
@@ -175,15 +172,14 @@
         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
-        if (!config.onlyPlayAtomicComponent()) {
-            // Don't update the scrim during the atomic animation.
+        if (config.playNonAtomicComponent()) {
             propertySetter.setInt(cl.getScrimBackground(),
                     DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
         }
         if (config.playAtomicOverviewScaleComponent()) {
             Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
                     pageAlphaProvider.interpolator);
-            propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
+            propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
                     pageAlpha, fadeInterpolator);
         }
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e085ff0..afb7217 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -64,8 +65,6 @@
 import com.android.launcher3.views.SpringRelativeLayout;
 import com.android.launcher3.views.WorkFooterContainer;
 
-import java.util.ArrayList;
-
 /**
  * The all apps view container.
  */
@@ -328,10 +327,12 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        parents.add(newContainerTarget(
-                getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        if (getApps().hasFilter()) {
+            targetParent.containerType = ContainerType.SEARCHRESULT;
+        } else {
+            targetParent.containerType = ContainerType.ALLAPPS;
+        }
     }
 
     @Override
@@ -392,7 +393,7 @@
         rebindAdapters(showTabs, false /* force */);
     }
 
-    protected void rebindAdapters(boolean showTabs, boolean force) {
+    private void rebindAdapters(boolean showTabs, boolean force) {
         if (showTabs == mUsingTabs && !force) {
             return;
         }
@@ -460,7 +461,6 @@
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
         reset(true /* animate */);
-        mViewPager.getPageIndicator().updateTabTextColor(pos);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
@@ -606,7 +606,6 @@
         public static final int MAIN = 0;
         public static final int WORK = 1;
 
-        private ItemInfoMatcher mInfoMatcher;
         private final boolean mIsWork;
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
@@ -626,7 +625,6 @@
         }
 
         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
-            mInfoMatcher = matcher;
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
@@ -647,14 +645,19 @@
         void setupOverlay() {
             if (!mIsWork || recyclerView == null) return;
             boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
-            if (mWorkDisabled == workDisabled) return;
+            recyclerView.getOverlay().clear();
             if (workDisabled) {
-                appsList.updateItemFilter((info, cn) -> false);
-                recyclerView.addAutoSizedOverlay(
-                        mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
-            } else if (mInfoMatcher != null) {
-                appsList.updateItemFilter(mInfoMatcher);
-                recyclerView.clearAutoSizedOverlays();
+                View pausedOverlay = mLauncher.getLayoutInflater().inflate(
+                        R.layout.work_apps_paused, null);
+                recyclerView.post(() -> {
+                    int width = recyclerView.getWidth();
+                    int height = recyclerView.getHeight() -  mWorkFooterContainer.getHeight();
+                    pausedOverlay.measure(makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+                            makeMeasureSpec(recyclerView.getHeight(), EXACTLY));
+                    pausedOverlay.layout(0, 0, width, height);
+                    applyPadding();
+                });
+                recyclerView.getOverlay().add(pausedOverlay);
             }
             mWorkDisabled = workDisabled;
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 8fe4633..b6744cf 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,11 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.UNSPECIFIED;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -44,7 +40,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -64,8 +59,6 @@
     private AllAppsBackgroundDrawable mEmptySearchBackground;
     private int mEmptySearchBackgroundTopOffset;
 
-    private ArrayList<View> mAutoSizedOverlays = new ArrayList<>();
-
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -149,37 +142,15 @@
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         updateEmptySearchBackgroundBounds();
         updatePoolSize();
-        for (int i = 0; i < mAutoSizedOverlays.size(); i++) {
-            View overlay = mAutoSizedOverlays.get(i);
-            overlay.measure(makeMeasureSpec(w, EXACTLY), makeMeasureSpec(w, EXACTLY));
-            overlay.layout(0, 0, w, h);
-        }
-    }
-
-    /**
-     * Adds an overlay that automatically rescales with the recyclerview.
-     */
-    public void addAutoSizedOverlay(View overlay) {
-        mAutoSizedOverlays.add(overlay);
-        getOverlay().add(overlay);
-        onSizeChanged(getWidth(), getHeight(), getWidth(), getHeight());
-    }
-
-    /**
-     * Clears auto scaling overlay views added by #addAutoSizedOverlay
-     */
-    public void clearAutoSizedOverlays() {
-        for (View v : mAutoSizedOverlays) {
-            getOverlay().remove(v);
-        }
-        mAutoSizedOverlays.clear();
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        parents.add(newContainerTarget(
-                getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        if (mApps.hasFilter()) {
+            targetParent.containerType = ContainerType.SEARCHRESULT;
+        } else {
+            targetParent.containerType = ContainerType.ALLAPPS;
+        }
     }
 
     public void onSearchResultsChanged() {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 744f4eb..2179162 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -12,6 +12,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -30,8 +31,11 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
+import com.android.systemui.plugins.ResourceProvider;
 
 /**
  * Handles AllApps view transition.
@@ -162,7 +166,7 @@
             return;
         }
 
-        if (config.onlyPlayAtomicComponent()) {
+        if (!config.playNonAtomicComponent()) {
             // There is no atomic component for the all apps transition, so just return early.
             return;
         }
@@ -181,6 +185,14 @@
     }
 
     public Animator createSpringAnimation(float... progressValues) {
+        if (UNSTABLE_SPRINGS.get()) {
+            ResourceProvider rp = DynamicResource.provider(mLauncher);
+            float damping = rp.getFloat(R.dimen.all_apps_spring_damping_ratio);
+            float stiffness = rp.getFloat(R.dimen.all_apps_spring_stiffness);
+
+            return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
+                    damping, stiffness, progressValues);
+        }
         return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
     }
 
@@ -212,7 +224,12 @@
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
-        return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
+        return new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                onProgressAnimationEnd();
+            }
+        };
     }
 
     public void setupViews(AllAppsContainerView appsView) {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 81e1b94..cc33af9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
@@ -372,7 +370,7 @@
         }
 
         allowTouchForwarding(hasAllAppsContent);
-        setter.setFloat(mTabLayout, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
+        setter.setFloat(mTabLayout, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
     }
 
     protected void allowTouchForwarding(boolean allow) {
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 3e40392..0e39bbe 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -71,10 +71,12 @@
         mIsRtl = Utilities.isRtl(getResources());
     }
 
-    /**
-     * Highlights tab with index pos
-     */
-    public void updateTabTextColor(int pos) {
+    private void updateIndicatorPosition(float scrollOffset) {
+        mScrollOffset = scrollOffset;
+        updateIndicatorPosition();
+    }
+
+    private void updateTabTextColor(int pos) {
         mSelectedPosition = pos;
         for (int i = 0; i < getChildCount(); i++) {
             Button tab = (Button) getChildAt(i);
@@ -82,11 +84,6 @@
         }
     }
 
-    private void updateIndicatorPosition(float scrollOffset) {
-        mScrollOffset = scrollOffset;
-        updateIndicatorPosition();
-    }
-
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 3089b18..535ef54 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -15,11 +15,10 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.ALPHA;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-
 import android.graphics.Rect;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -68,7 +67,7 @@
     public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
             PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
         // Don't use setViewAlpha as we want to control the visibility ourselves.
-        setter.setFloat(mView, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
+        setter.setFloat(mView, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index 9905e81..9448632 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -39,25 +39,4 @@
     }
 
     public abstract void onAnimationSuccess(Animator animator);
-
-    /**
-     * Returns an AnimationSuccessListener which runs the provided action on success
-     */
-    public static AnimationSuccessListener forRunnable(Runnable r) {
-        return new RunnableSuccessListener(r);
-    }
-
-    private static class RunnableSuccessListener extends AnimationSuccessListener {
-
-        private final Runnable mRunnable;
-
-        private RunnableSuccessListener(Runnable r) {
-            mRunnable = r;
-        }
-
-        @Override
-        public void onAnimationSuccess(Animator animator) {
-            mRunnable.run();
-        }
-    }
 }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 1fc21fd..1c277ab 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -16,9 +16,7 @@
 package com.android.launcher3.anim;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -26,18 +24,17 @@
 import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.content.Context;
-import android.util.FloatProperty;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
-
-import com.android.launcher3.Utilities;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
+import java.util.Set;
 
 /**
  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
@@ -46,7 +43,14 @@
  * Note: The implementation does not support start delays on child animations or
  * sequential playbacks.
  */
-public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
+public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
+
+    private static final String TAG = "AnimatorPlaybackCtrler";
+    private static boolean DEBUG = false;
+
+    public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+        return wrap(anim, duration, null);
+    }
 
     /**
      * Creates an animation controller for the provided animation.
@@ -54,41 +58,20 @@
      * needs to be larger than the total number of pixels so that we don't have jittering due
      * to float (animation-fraction * total duration) to int conversion.
      */
-    public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+    public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
+            Runnable onCancelRunnable) {
+
         /**
          * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
          */
-        ArrayList<Holder> childAnims = new ArrayList<>();
-        addAnimationHoldersRecur(anim, SpringProperty.DEFAULT, childAnims);
-
-        return new AnimatorPlaybackController(anim, duration, childAnims);
+        return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
     }
 
-    public static AnimatorPlaybackController wrap(PendingAnimation anim, long duration) {
-        /**
-         * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
-         */
-        return new AnimatorPlaybackController(anim.anim, duration, anim.animHolders);
-    }
-
-    private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
-            new FloatProperty<ValueAnimator>("current-play-time") {
-                @Override
-                public void setValue(ValueAnimator animator, float v) {
-                    animator.setCurrentPlayTime((long) v);
-                }
-
-                @Override
-                public Float get(ValueAnimator animator) {
-                    return (float) animator.getCurrentPlayTime();
-                }
-            };
-
     private final ValueAnimator mAnimationPlayer;
     private final long mDuration;
 
-    private final AnimatorSet mAnim;
-    private final Holder[] mChildAnimations;
+    protected final AnimatorSet mAnim;
+    private Set<SpringAnimation> mSprings;
 
     protected float mCurrentFraction;
     private Runnable mEndAction;
@@ -96,14 +79,22 @@
     protected boolean mTargetCancelled = false;
     protected Runnable mOnCancelRunnable;
 
-    private AnimatorPlaybackController(
-            AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
+    private OnAnimationEndDispatcher mEndListener;
+    private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
+    // We need this variable to ensure the end listener is called immediately, otherwise we run into
+    // issues where the callback interferes with the states of the swipe detector.
+    private boolean mSkipToEnd = false;
+
+    protected AnimatorPlaybackController(AnimatorSet anim, long duration,
+            Runnable onCancelRunnable) {
         mAnim = anim;
         mDuration = duration;
+        mOnCancelRunnable = onCancelRunnable;
 
         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
         mAnimationPlayer.setInterpolator(LINEAR);
-        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+        mEndListener = new OnAnimationEndDispatcher();
+        mAnimationPlayer.addListener(mEndListener);
         mAnimationPlayer.addUpdateListener(this);
 
         mAnim.addListener(new AnimatorListenerAdapter() {
@@ -128,7 +119,14 @@
             }
         });
 
-        mChildAnimations = childAnims.toArray(new Holder[childAnims.size()]);
+        mSprings = new HashSet<>();
+        mSpringEndListener = (animation, canceled, value, velocity1) -> {
+            if (canceled) {
+                mEndListener.onAnimationCancel(mAnimationPlayer);
+            } else {
+                mEndListener.onAnimationEnd(mAnimationPlayer);
+            }
+        };
     }
 
     public AnimatorSet getTarget() {
@@ -162,68 +160,9 @@
     }
 
     /**
-     * Starts playing the animation with the provided velocity optionally playing any
-     * physics based animations
-     */
-    public void startWithVelocity(Context context, boolean goingToEnd,
-            float velocity, float scale, long animationDuration) {
-        float scaleInverse = 1 / Math.abs(scale);
-        float scaledVelocity = velocity * scaleInverse;
-
-        float nextFrameProgress = Utilities.boundToRange(getProgressFraction()
-                + scaledVelocity * getSingleFrameMs(context), 0f, 1f);
-
-        // Update setters for spring
-        int springFlag = goingToEnd
-                ? SpringProperty.FLAG_CAN_SPRING_ON_END
-                : SpringProperty.FLAG_CAN_SPRING_ON_START;
-
-        long springDuration = animationDuration;
-        for (Holder h : mChildAnimations) {
-            if ((h.springProperty.flags & springFlag) != 0) {
-                SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME)
-                        .setStartValue(clampDuration(mCurrentFraction))
-                        .setEndValue(goingToEnd ? h.anim.getDuration() : 0)
-                        .setStartVelocity(scaledVelocity * h.anim.getDuration())
-                        .setMinimumVisibleChange(scaleInverse)
-                        .setDampingRatio(h.springProperty.mDampingRatio)
-                        .setStiffness(h.springProperty.mStiffness);
-
-                long expectedDurationL = s.build(context).getDuration();
-                springDuration = Math.max(expectedDurationL, springDuration);
-
-                float expectedDuration = expectedDurationL;
-                h.setter = (a, l) ->
-                    s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration);
-                h.anim.setInterpolator(LINEAR);
-            }
-        }
-
-        mAnimationPlayer.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
-
-        if (springDuration <= animationDuration) {
-            mAnimationPlayer.setDuration(animationDuration);
-            mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocity));
-        } else {
-            // Since spring requires more time to run, we let the other animations play with
-            // current time and interpolation and by clamping the duration.
-            mAnimationPlayer.setDuration(springDuration);
-
-            float cutOff = animationDuration / (float) springDuration;
-            mAnimationPlayer.setInterpolator(
-                    clampToProgress(scrollInterpolatorForVelocity(velocity), 0, cutOff));
-        }
-        mAnimationPlayer.start();
-    }
-
-    /**
      * Pauses the currently playing animation.
      */
     public void pause() {
-        // Reset property setters
-        for (Holder h : mChildAnimations) {
-            h.reset();
-        }
         mAnimationPlayer.cancel();
     }
 
@@ -237,18 +176,7 @@
     /**
      * Sets the current animation position and updates all the child animators accordingly.
      */
-    public void setPlayFraction(float fraction) {
-        mCurrentFraction = fraction;
-        // Let the animator report the progress but don't apply the progress to child
-        // animations if it has been cancelled.
-        if (mTargetCancelled) {
-            return;
-        }
-        long playPos = clampDuration(fraction);
-        for (Holder holder : mChildAnimations) {
-            holder.setter.set(holder.anim, playPos);
-        }
-    }
+    public abstract void setPlayFraction(float fraction);
 
     public float getProgressFraction() {
         return mCurrentFraction;
@@ -280,6 +208,49 @@
         }
     }
 
+    /**
+     * Starts playback and sets the spring.
+     */
+    public void dispatchOnStartWithVelocity(float end, float velocity) {
+        if (!QUICKSTEP_SPRINGS.get()) {
+            dispatchOnStart();
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
+
+        for (Animator a : mAnim.getChildAnimations()) {
+            if (a instanceof SpringObjectAnimator) {
+                if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
+                SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
+                mSprings.add(springAnimator.getSpring());
+                springAnimator.startSpring(end, velocity, mSpringEndListener);
+            }
+        }
+
+        dispatchOnStart();
+    }
+
+    public void dispatchOnStart() {
+        dispatchOnStartRecursively(mAnim);
+    }
+
+    private void dispatchOnStartRecursively(Animator animator) {
+        List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator
+                ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners())
+                : nonNullList(animator.getListeners());
+
+        for (AnimatorListener l : listeners) {
+            l.onAnimationStart(animator);
+        }
+
+        if (animator instanceof AnimatorSet) {
+            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                dispatchOnStartRecursively(anim);
+            }
+        }
+    }
+
     /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
     public void dispatchOnCancelWithoutCancelRunnable() {
         dispatchOnCancelWithoutCancelRunnable(null);
@@ -301,47 +272,115 @@
         setOnCancelRunnable(onCancel);
     }
 
-
-    public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) {
-        mOnCancelRunnable = runnable;
-        return this;
-    }
-
-    public void dispatchOnStart() {
-        callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart);
-    }
-
     public void dispatchOnCancel() {
-        callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationCancel);
+        dispatchOnCancelRecursively(mAnim);
+    }
+
+    private void dispatchOnCancelRecursively(Animator animator) {
+        for (AnimatorListener l : nonNullList(animator.getListeners())) {
+            l.onAnimationCancel(animator);
+        }
+
+        if (animator instanceof AnimatorSet) {
+            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                dispatchOnCancelRecursively(anim);
+            }
+        }
     }
 
     public void dispatchSetInterpolator(TimeInterpolator interpolator) {
-        callAnimatorCommandRecursively(mAnim, a -> a.setInterpolator(interpolator));
+        dispatchSetInterpolatorRecursively(mAnim, interpolator);
     }
 
-    private static void callListenerCommandRecursively(
-            Animator anim, BiConsumer<AnimatorListener, Animator> command) {
-        callAnimatorCommandRecursively(anim, a-> {
-            for (AnimatorListener l : nonNullList(a.getListeners())) {
-                command.accept(l, a);
-            }
-        });
-    }
-
-    private static void callAnimatorCommandRecursively(Animator anim, Consumer<Animator> command) {
-        command.accept(anim);
+    private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) {
+        anim.setInterpolator(interpolator);
         if (anim instanceof AnimatorSet) {
             for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) {
-                callAnimatorCommandRecursively(child, command);
+                dispatchSetInterpolatorRecursively(child, interpolator);
             }
         }
     }
 
+    public void setOnCancelRunnable(Runnable runnable) {
+        mOnCancelRunnable = runnable;
+    }
+
+    public void skipToEnd() {
+        mSkipToEnd = true;
+        for (SpringAnimation spring : mSprings) {
+            if (spring.canSkipToEnd()) {
+                spring.skipToEnd();
+            }
+        }
+        mAnimationPlayer.end();
+        mSkipToEnd = false;
+    }
+
+    public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
+
+        private final ValueAnimator[] mChildAnimations;
+
+        private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
+                Runnable onCancelRunnable) {
+            super(anim, duration, onCancelRunnable);
+
+            // Build animation list
+            ArrayList<ValueAnimator> childAnims = new ArrayList<>();
+            getAnimationsRecur(mAnim, childAnims);
+            mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
+        }
+
+        private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
+            long forceDuration = anim.getDuration();
+            TimeInterpolator forceInterpolator = anim.getInterpolator();
+            for (Animator child : anim.getChildAnimations()) {
+                if (forceDuration > 0) {
+                    child.setDuration(forceDuration);
+                }
+                if (forceInterpolator != null) {
+                    child.setInterpolator(forceInterpolator);
+                }
+                if (child instanceof ValueAnimator) {
+                    out.add((ValueAnimator) child);
+                } else if (child instanceof AnimatorSet) {
+                    getAnimationsRecur((AnimatorSet) child, out);
+                } else {
+                    throw new RuntimeException("Unknown animation type " + child);
+                }
+            }
+        }
+
+        @Override
+        public void setPlayFraction(float fraction) {
+            mCurrentFraction = fraction;
+            // Let the animator report the progress but don't apply the progress to child
+            // animations if it has been cancelled.
+            if (mTargetCancelled) {
+                return;
+            }
+            long playPos = clampDuration(fraction);
+            for (ValueAnimator anim : mChildAnimations) {
+                anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+            }
+        }
+    }
+
+    private boolean isAnySpringRunning() {
+        for (SpringAnimation spring : mSprings) {
+            if (spring.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Only dispatches the on end actions once the animator and all springs have completed running.
      */
     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
 
+        boolean mAnimatorDone = false;
+        boolean mSpringsDone = false;
         boolean mDispatched = false;
 
         @Override
@@ -352,76 +391,39 @@
 
         @Override
         public void onAnimationSuccess(Animator animator) {
+            if (mSprings.isEmpty()) {
+                mSpringsDone = mAnimatorDone = true;
+            }
+            if (isAnySpringRunning()) {
+                mAnimatorDone = true;
+            } else {
+                mSpringsDone = true;
+            }
+
             // We wait for the spring (if any) to finish running before completing the end callback.
-            if (!mDispatched) {
-                callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd);
+            if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) {
+                dispatchOnEndRecursively(mAnim);
                 if (mEndAction != null) {
                     mEndAction.run();
                 }
                 mDispatched = true;
             }
         }
+
+        private void dispatchOnEndRecursively(Animator animator) {
+            for (AnimatorListener l : nonNullList(animator.getListeners())) {
+                l.onAnimationEnd(animator);
+            }
+
+            if (animator instanceof AnimatorSet) {
+                for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                    dispatchOnEndRecursively(anim);
+                }
+            }
+        }
     }
 
     private static <T> List<T> nonNullList(ArrayList<T> list) {
         return list == null ? Collections.emptyList() : list;
     }
-
-    /**
-     * Interface for setting position of value animator
-     */
-    private interface PositionSetter {
-
-        PositionSetter DEFAULT = (anim, playPos) ->
-                anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
-
-        void set(ValueAnimator anim, long position);
-    }
-
-    /**
-     * Holder class for various child animations
-     */
-    static class Holder {
-
-        public final ValueAnimator anim;
-
-        public final SpringProperty springProperty;
-
-        public final TimeInterpolator interpolator;
-
-        public PositionSetter setter;
-
-        Holder(Animator anim, SpringProperty springProperty) {
-            this.anim = (ValueAnimator) anim;
-            this.springProperty = springProperty;
-            this.interpolator = this.anim.getInterpolator();
-            this.setter = PositionSetter.DEFAULT;
-        }
-
-        public void reset() {
-            anim.setInterpolator(interpolator);
-            setter = PositionSetter.DEFAULT;
-        }
-    }
-
-    static void addAnimationHoldersRecur(
-            Animator anim, SpringProperty springProperty, ArrayList<Holder> out) {
-        long forceDuration = anim.getDuration();
-        TimeInterpolator forceInterpolator = anim.getInterpolator();
-        if (anim instanceof ValueAnimator) {
-            out.add(new Holder(anim, springProperty));
-        } else if (anim instanceof AnimatorSet) {
-            for (Animator child : ((AnimatorSet) anim).getChildAnimations()) {
-                if (forceDuration > 0) {
-                    child.setDuration(forceDuration);
-                }
-                if (forceInterpolator != null) {
-                    child.setInterpolator(forceInterpolator);
-                }
-                addAnimationHoldersRecur(child, springProperty, out);
-            }
-        } else {
-            throw new RuntimeException("Unknown animation type " + anim);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index d814b19..cd30dea 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -21,6 +21,7 @@
 import android.view.animation.Interpolator;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Utility class for building animator set
@@ -41,17 +42,36 @@
     public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
 
+    public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
+
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
     private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
+    private List<Runnable> mOnFinishRunnables = new ArrayList<>();
+    private int mFlags = 0;
 
     public void play(Animator anim) {
         mAnims.add(anim);
     }
 
+    public void addOnFinishRunnable(Runnable onFinishRunnable) {
+        mOnFinishRunnables.add(onFinishRunnable);
+    }
+
     public AnimatorSet build() {
         AnimatorSet anim = new AnimatorSet();
         anim.playTogether(mAnims);
+        if (!mOnFinishRunnables.isEmpty()) {
+            anim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animation) {
+                    for (Runnable onFinishRunnable : mOnFinishRunnables) {
+                        onFinishRunnable.run();
+                    }
+                    mOnFinishRunnables.clear();
+                }
+            });
+        }
         return anim;
     }
 
@@ -62,4 +82,12 @@
     public void setInterpolator(int animId, Interpolator interpolator) {
         mInterpolators.put(animId, interpolator);
     }
+
+    public void addFlag(int flag) {
+        mFlags |= flag;
+    }
+
+    public boolean hasFlag(int flag) {
+        return (mFlags & flag) != 0;
+    }
 }
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
deleted file mode 100644
index 562d160..0000000
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2018 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.anim;
-
-import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Utility class to keep track of a running animation.
- *
- * This class allows attaching end callbacks to an animation is intended to be used with
- * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
- * AnimationListeners are not properly dispatched.
- *
- * TODO: Find a better name
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class PendingAnimation {
-
-    private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
-
-    /** package private **/
-    final AnimatorSet anim = new AnimatorSet();
-    final ArrayList<Holder> animHolders = new ArrayList<>();
-
-    /**
-     * Utility method to sent an interpolator on an animation and add it to the list
-     */
-    public void add(Animator anim, TimeInterpolator interpolator) {
-        add(anim, interpolator, SpringProperty.DEFAULT);
-    }
-
-    public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
-        anim.setInterpolator(interpolator);
-        add(anim, springProperty);
-    }
-
-    public void add(Animator anim) {
-        add(anim, SpringProperty.DEFAULT);
-    }
-
-    public void add(Animator a, SpringProperty springProperty) {
-        anim.play(a);
-        addAnimationHoldersRecur(a, springProperty, animHolders);
-    }
-
-    public void finish(boolean isSuccess, int logAction) {
-        for (Consumer<EndState> listeners : mEndListeners) {
-            listeners.accept(new EndState(isSuccess, logAction));
-        }
-        mEndListeners.clear();
-    }
-
-    public void addEndListener(Consumer<EndState> listener) {
-        mEndListeners.add(listener);
-    }
-
-    public static class EndState {
-        public boolean isSuccess;
-        public int logAction;
-
-        public EndState(boolean isSuccess, int logAction) {
-            this.isSuccess = isSuccess;
-            this.logAction = logAction;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 0b2eb48..757edff 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -19,8 +19,7 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.util.FloatProperty;
-import android.util.IntProperty;
+import android.util.Property;
 import android.view.View;
 
 /**
@@ -37,14 +36,14 @@
         }
     }
 
-    public <T> void setFloat(T target, FloatProperty<T> property, float value,
+    public <T> void setFloat(T target, Property<T, Float> property, float value,
             TimeInterpolator interpolator) {
-        property.setValue(target, value);
+        property.set(target, value);
     }
 
-    public <T> void setInt(T target, IntProperty<T> property, int value,
+    public <T> void setInt(T target, Property<T, Integer> property, int value,
             TimeInterpolator interpolator) {
-        property.setValue(target, value);
+        property.set(target, value);
     }
 
     public static class AnimatedPropertySetter extends PropertySetter {
@@ -69,7 +68,7 @@
         }
 
         @Override
-        public <T> void setFloat(T target, FloatProperty<T> property, float value,
+        public <T> void setFloat(T target, Property<T, Float> property, float value,
                 TimeInterpolator interpolator) {
             if (property.get(target) == value) {
                 return;
@@ -80,7 +79,7 @@
         }
 
         @Override
-        public <T> void setInt(T target, IntProperty<T> property, int value,
+        public <T> void setInt(T target, Property<T, Integer> property, int value,
                 TimeInterpolator interpolator) {
             if (property.get(target) == value) {
                 return;
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index f22a9f0..0f34c1e 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -15,15 +15,16 @@
  */
 package com.android.launcher3.anim;
 
+import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.FloatProperty;
 
+import com.android.launcher3.util.DefaultDisplay;
+
 import androidx.annotation.FloatRange;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.util.DefaultDisplay;
-
 /**
  * Utility class to build an object animator which follows the same path as a spring animation for
  * an underdamped spring.
@@ -191,8 +192,12 @@
         long durationMs = (long) (1000.0 * duration);
         ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
         animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
-        animator.addListener(AnimationSuccessListener.forRunnable(
-                () -> mProperty.setValue(mTarget, mEndValue)));
+        animator.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mProperty.setValue(mTarget, mEndValue);
+            }
+        });
         return animator;
     }
 
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
new file mode 100644
index 0000000..27b9c18
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -0,0 +1,308 @@
+/*
+ * 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.anim;
+
+import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
+
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.FloatProperty;
+import android.util.Log;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import java.util.ArrayList;
+
+
+/**
+ * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
+ * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
+ */
+public class SpringObjectAnimator<T> extends ValueAnimator {
+
+    private static final String TAG = "SpringObjectAnimator";
+    private static boolean DEBUG = false;
+
+    private ObjectAnimator mObjectAnimator;
+    private float[] mValues;
+
+    private SpringAnimation mSpring;
+    private SpringProperty<T> mProperty;
+
+    private ArrayList<AnimatorListener> mListeners;
+    private boolean mSpringEnded = true;
+    private boolean mAnimatorEnded = true;
+    private boolean mEnded = true;
+
+    public SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange,
+            float damping, float stiffness, float... values) {
+        mSpring = new SpringAnimation(object, createFloatPropertyCompat(property));
+        mSpring.setMinimumVisibleChange(minimumVisibleChange);
+        mSpring.setSpring(new SpringForce(0)
+                .setDampingRatio(damping)
+                .setStiffness(stiffness));
+        mSpring.setStartVelocity(0.01f);
+        mProperty = new SpringProperty<>(property, mSpring);
+        mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
+        mValues = values;
+        mListeners = new ArrayList<>();
+        setFloatValues(values);
+
+        // We use this listener and track mListeners so that we can sync the animator and spring
+        // listeners.
+        mObjectAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mAnimatorEnded = false;
+                mEnded = false;
+                for (AnimatorListener l : mListeners) {
+                    l.onAnimationStart(animation);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimatorEnded = true;
+                tryEnding();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                for (AnimatorListener l : mListeners) {
+                    l.onAnimationCancel(animation);
+                }
+                mSpring.cancel();
+            }
+        });
+
+        mSpring.addUpdateListener((animation, value, velocity) -> {
+            mSpringEnded = false;
+            mEnded = false;
+        });
+        mSpring.addEndListener((animation, canceled, value, velocity) -> {
+            mSpringEnded = true;
+            tryEnding();
+        });
+    }
+
+    private void tryEnding() {
+        if (DEBUG) {
+            Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded="
+                    + mSpringEnded + ", mEnded=" + mEnded);
+        }
+
+        // If springs are disabled, ignore value of mSpringEnded
+        if (mAnimatorEnded && (mSpringEnded || !QUICKSTEP_SPRINGS.get()) && !mEnded) {
+            for (AnimatorListener l : mListeners) {
+                l.onAnimationEnd(this);
+            }
+            mEnded = true;
+        }
+    }
+
+    public SpringAnimation getSpring() {
+        return mSpring;
+    }
+
+    /**
+     * Initializes and sets up the spring to take over controlling the object.
+     */
+    public void startSpring(float end, float velocity, OnAnimationEndListener endListener) {
+        // Cancel the spring so we can set new start velocity and final position. We need to remove
+        // the listener since the spring is not actually ending.
+        mSpring.removeEndListener(endListener);
+        mSpring.cancel();
+        mSpring.addEndListener(endListener);
+
+        mProperty.switchToSpring();
+
+        float startValue = end == 0 ? mValues[1] : mValues[0];
+        float endValue = end == 0 ? mValues[0] : mValues[1];
+
+        // Ensures that the velocity matches the direction of the values.
+        velocity = Math.signum(endValue - startValue) * Math.abs(velocity);
+        mSpring.setStartVelocity(velocity);
+
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            mSpring.animateToFinalPosition(endValue);
+        }, getStartDelay());
+    }
+
+    @Override
+    public void addListener(AnimatorListener listener) {
+        mListeners.add(listener);
+    }
+
+    public ArrayList<AnimatorListener> getObjectAnimatorListeners() {
+        return mObjectAnimator.getListeners();
+    }
+
+    @Override
+    public ArrayList<AnimatorListener> getListeners() {
+        return mListeners;
+    }
+
+    @Override
+    public void removeAllListeners() {
+        mListeners.clear();
+    }
+
+    @Override
+    public void removeListener(AnimatorListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void addPauseListener(AnimatorPauseListener listener) {
+        mObjectAnimator.addPauseListener(listener);
+    }
+
+    @Override
+    public void cancel() {
+        mObjectAnimator.cancel();
+        mSpring.cancel();
+    }
+
+    @Override
+    public void end() {
+        mObjectAnimator.end();
+    }
+
+    @Override
+    public long getDuration() {
+        return mObjectAnimator.getDuration();
+    }
+
+    @Override
+    public TimeInterpolator getInterpolator() {
+        return mObjectAnimator.getInterpolator();
+    }
+
+    @Override
+    public long getStartDelay() {
+        return mObjectAnimator.getStartDelay();
+    }
+
+    @Override
+    public long getTotalDuration() {
+        return mObjectAnimator.getTotalDuration();
+    }
+
+    @Override
+    public boolean isPaused() {
+        return mObjectAnimator.isPaused();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mObjectAnimator.isRunning();
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mObjectAnimator.isStarted();
+    }
+
+    @Override
+    public void pause() {
+        mObjectAnimator.pause();
+    }
+
+    @Override
+    public void removePauseListener(AnimatorPauseListener listener) {
+        mObjectAnimator.removePauseListener(listener);
+    }
+
+    @Override
+    public void resume() {
+        mObjectAnimator.resume();
+    }
+
+    @Override
+    public ValueAnimator setDuration(long duration) {
+        return mObjectAnimator.setDuration(duration);
+    }
+
+    @Override
+    public void setInterpolator(TimeInterpolator value) {
+        mObjectAnimator.setInterpolator(value);
+    }
+
+    @Override
+    public void setStartDelay(long startDelay) {
+        mObjectAnimator.setStartDelay(startDelay);
+    }
+
+    @Override
+    public void setTarget(Object target) {
+        mObjectAnimator.setTarget(target);
+    }
+
+    @Override
+    public void start() {
+        mObjectAnimator.start();
+    }
+
+    @Override
+    public void setCurrentFraction(float fraction) {
+        mObjectAnimator.setCurrentFraction(fraction);
+    }
+
+    @Override
+    public void setCurrentPlayTime(long playTime) {
+        mObjectAnimator.setCurrentPlayTime(playTime);
+    }
+
+    public static class SpringProperty<T> extends FloatProperty<T> {
+
+        boolean useSpring = false;
+        final FloatProperty<T> mProperty;
+        final SpringAnimation mSpring;
+
+        public SpringProperty(FloatProperty<T> property, SpringAnimation spring) {
+            super(property.getName());
+            mProperty = property;
+            mSpring = spring;
+        }
+
+        public void switchToSpring() {
+            useSpring = true;
+        }
+
+        @Override
+        public Float get(T object) {
+            return mProperty.get(object);
+        }
+
+        @Override
+        public void setValue(T object, float progress) {
+            if (useSpring) {
+                mSpring.animateToFinalPosition(progress);
+            } else {
+                mProperty.setValue(object, progress);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/anim/SpringProperty.java b/src/com/android/launcher3/anim/SpringProperty.java
deleted file mode 100644
index caedd6c..0000000
--- a/src/com/android/launcher3/anim/SpringProperty.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.launcher3.anim;
-
-import androidx.dynamicanimation.animation.SpringForce;
-
-/**
- * Utility class to store configurations for spring animation
- */
-public class SpringProperty {
-
-    public static final SpringProperty DEFAULT = new SpringProperty();
-
-    // Play spring when the animation is going towards the end
-    public static final int FLAG_CAN_SPRING_ON_END = 1 << 0;
-    // Play spring when animation is going towards the start (in reverse direction)
-    public static final int FLAG_CAN_SPRING_ON_START = 1 << 1;
-
-    public final int flags;
-
-    float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-    float mStiffness = SpringForce.STIFFNESS_MEDIUM;
-
-    public SpringProperty() {
-        this(0);
-    }
-
-    public SpringProperty(int flags) {
-        this.flags = flags;
-    }
-
-    public SpringProperty setDampingRatio(float dampingRatio) {
-        mDampingRatio = dampingRatio;
-        return this;
-    }
-
-    public SpringProperty setStiffness(float stiffness) {
-        mStiffness = stiffness;
-        return this;
-    }
-}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 471a743..ed28df0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -36,8 +36,6 @@
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
-    public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
-            "ENABLE_FIXED_ROTATION_TRANSFORM";
 
     private FeatureFlags() { }
 
@@ -95,9 +93,8 @@
     public static final BooleanFlag FAKE_LANDSCAPE_UI = getDebugFlag(
             "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
 
-    public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
-            "FOLDER_NAME_SUGGEST", true,
-            "Suggests folder names instead of blank text.");
+    public static final BooleanFlag FOLDER_NAME_SUGGEST = getDebugFlag(
+            "FOLDER_NAME_SUGGEST", true, "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
@@ -155,10 +152,6 @@
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
-    public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
-            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
-            "Launch/close apps without rotation animation. Fix Launcher to portrait");
-
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 369bf28..92f35e2 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -119,7 +119,6 @@
         recreateControllers();
     }
 
-    @Override
     public void recreateControllers() {
         mControllers = mActivity.createTouchControllers();
     }
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 77c6306..869dd94 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetManager;
 import android.content.pm.LauncherApps.PinItemRequest;
@@ -40,8 +38,6 @@
 import com.android.launcher3.widget.PendingItemDragHelper;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 
-import java.util.ArrayList;
-
 /**
  * {@link DragSource} for handling drop from a different window. This object is initialized
  * in the source window and is passed on to the Launcher activity as an Intent extra.
@@ -107,9 +103,9 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> parents) {
-        parents.add(newContainerTarget(LauncherLogProto.ContainerType.PINITEM));
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        targetParent.containerType = LauncherLogProto.ContainerType.PINITEM;
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2be8ff4..69f93de 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -27,7 +27,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
@@ -90,6 +89,7 @@
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.userevent.LauncherLogProto.Action;
 import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
@@ -1459,24 +1459,12 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> targets) {
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = mContent.getCurrentPage();
-
-        LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.FOLDER);
-        target.pageIndex = mInfo.screenId;
-        target.gridX = mInfo.cellX;
-        target.gridY = mInfo.cellY;
-        targets.add(target);
-
-        // continue to parent
-        if (mInfo.container == CONTAINER_HOTSEAT) {
-            mLauncher.getHotseat().fillInLogContainerData(mInfo, target, targets);
-        } else {
-            mLauncher.getWorkspace().fillInLogContainerData(mInfo, target, targets);
-        }
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        target.gridX = info.cellX;
+        target.gridY = info.cellY;
+        target.pageIndex = mContent.getCurrentPage();
+        targetParent.containerType = LauncherLogProto.ContainerType.FOLDER;
     }
 
     private class OnScrollHintListener implements OnAlarmListener {
@@ -1609,7 +1597,7 @@
                     }
                 } else {
                     mLauncher.getUserEventDispatcher().logActionTapOutside(
-                            newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
+                            LoggerUtils.newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
                     close(true);
                     return true;
                 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index f72e674..1310d37 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,7 +46,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
@@ -222,13 +220,6 @@
         Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
         play(a, z, mIsOpening ? midDuration : 0, midDuration);
 
-        BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
-        int stateBackgroundBlur = mLauncher.getStateManager().getState()
-                .getBackgroundBlurRadius(mLauncher);
-        int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment();
-        play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening
-                ? stateBackgroundBlur + folderBackgroundBlurAdjustment
-                : stateBackgroundBlur));
 
         // Store clip variables
         CellLayout cellLayout = mContent.getCurrentCellLayout();
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 07161da..184dbb9 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,10 +15,8 @@
  */
 package com.android.launcher3.folder;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.os.Process;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -36,9 +34,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -100,23 +101,27 @@
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
-        Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
-                .collect(Collectors.toSet());
-        if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
+        List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
+                .filter(distinctByKey(p -> p.user))
+                .collect(Collectors.toList());
+
+        if (distinctItemInfos.size() == 1
+                && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
+            // Place it as last viable suggestion
             setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
-        Set<String> packageNames = workspaceItemInfos.stream()
-                .map(WorkspaceItemInfo::getTargetComponent)
-                .filter(Objects::nonNull)
-                .map(ComponentName::getPackageName)
-                .collect(Collectors.toSet());
+        distinctItemInfos = workspaceItemInfos.stream()
+                .filter(distinctByKey(p -> p.getTargetComponent() != null
+                        ? p.getTargetComponent().getPackageName() : ""))
+                .collect(Collectors.toList());
 
-        if (packageNames.size() == 1) {
-            Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
+        if (distinctItemInfos.size() == 1) {
+            Optional<AppInfo> info = getAppInfoByPackageName(
+                    distinctItemInfos.get(0).getTargetComponent().getPackageName());
             // Place it as first viable suggestion and shift everything else
             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
@@ -130,7 +135,6 @@
             return Optional.empty();
         }
         return mAppInfos.stream()
-                .filter(info -> info.componentName != null)
                 .filter(info -> info.componentName.getPackageName().equals(packageName))
                 .findAny();
     }
@@ -170,6 +174,12 @@
                         label.toString()));
     }
 
+    // This method can be moved to some Utility class location.
+    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
+        Map<Object, Boolean> map = new ConcurrentHashMap<>();
+        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
+    }
+
     private class FolderNameWorker extends BaseModelUpdateTask {
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
index f90962d..67b2b6d 100644
--- a/src/com/android/launcher3/graphics/Scrim.java
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.graphics.Canvas;
-import android.util.FloatProperty;
+import android.util.Property;
 import android.view.View;
 
 import com.android.launcher3.Launcher;
@@ -31,16 +31,16 @@
 public class Scrim implements View.OnAttachStateChangeListener,
         WallpaperColorInfo.OnChangeListener {
 
-    public static final FloatProperty<Scrim> SCRIM_PROGRESS =
-            new FloatProperty<Scrim>("scrimProgress") {
+    public static final Property<Scrim, Float> SCRIM_PROGRESS =
+            new Property<Scrim, Float>(Float.TYPE, "scrimProgress") {
                 @Override
                 public Float get(Scrim scrim) {
                     return scrim.mScrimProgress;
                 }
 
                 @Override
-                public void setValue(Scrim scrim, float v) {
-                    scrim.setScrimProgress(v);
+                public void set(Scrim scrim, Float value) {
+                    scrim.setScrimProgress(value);
                 }
             };
 
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 83349bc..5a1dcab 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -37,7 +37,7 @@
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.util.DisplayMetrics;
-import android.util.FloatProperty;
+import android.util.Property;
 import android.view.View;
 
 import androidx.core.graphics.ColorUtils;
@@ -54,28 +54,28 @@
  */
 public class WorkspaceAndHotseatScrim extends Scrim {
 
-    public static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_PROGRESS =
-            new FloatProperty<WorkspaceAndHotseatScrim>("sysUiProgress") {
+    public static final Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS =
+            new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") {
                 @Override
                 public Float get(WorkspaceAndHotseatScrim scrim) {
                     return scrim.mSysUiProgress;
                 }
 
                 @Override
-                public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+                public void set(WorkspaceAndHotseatScrim scrim, Float value) {
                     scrim.setSysUiProgress(value);
                 }
             };
 
-    private static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_ANIM_MULTIPLIER =
-            new FloatProperty<WorkspaceAndHotseatScrim>("sysUiAnimMultiplier") {
+    private static final Property<WorkspaceAndHotseatScrim, Float> SYSUI_ANIM_MULTIPLIER =
+            new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiAnimMultiplier") {
                 @Override
                 public Float get(WorkspaceAndHotseatScrim scrim) {
                     return scrim.mSysUiAnimMultiplier;
                 }
 
                 @Override
-                public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
+                public void set(WorkspaceAndHotseatScrim scrim, Float value) {
                     scrim.mSysUiAnimMultiplier = value;
                     scrim.reapplySysUiAlpha();
                 }
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index a9d10d7..b004edf 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -37,7 +37,6 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 
 /**
  * Helper methods for logging.
@@ -256,13 +255,4 @@
         event.action = action;
         return event;
     }
-
-    /**
-     * Creates LauncherEvent using Action and ArrayList of Targets
-     */
-    public static LauncherEvent newLauncherEvent(Action action, ArrayList<Target> targets) {
-        Target[] targetsArray = new Target[targets.size()];
-        targets.toArray(targetsArray);
-        return newLauncherEvent(action, targetsArray);
-    }
 }
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index 8449612..b02a050 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -5,13 +5,11 @@
 import android.view.View;
 import android.view.ViewParent;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
-import java.util.ArrayList;
+import androidx.annotation.Nullable;
 
 
 public class StatsLogUtils {
@@ -37,9 +35,14 @@
     public interface LogContainerProvider {
 
         /**
-         * Populates parent container targets for an item
+         * Copies data from the source to the destination proto.
+         *
+         * @param v            source of the data
+         * @param info         source of the data
+         * @param target       dest of the data
+         * @param targetParent dest of the data
          */
-        void fillInLogContainerData(ItemInfo childInfo, Target child, ArrayList<Target> parents);
+        void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 3770d17..afa3f6d 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -25,9 +25,6 @@
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 
 import static java.util.Optional.ofNullable;
 
@@ -51,7 +48,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.userevent.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -60,11 +57,7 @@
 import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
-import com.google.protobuf.nano.MessageNano;
-
-import java.util.ArrayList;
+import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -79,10 +72,8 @@
     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.USEREVENT);
     private static final String UUID_STORAGE = "uuid";
 
-    /**
-     * A factory method for UserEventDispatcher
-     */
-    public static UserEventDispatcher newInstance(Context context) {
+    public static UserEventDispatcher newInstance(Context context,
+            UserEventDelegate delegate) {
         SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
         String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
         if (uuidStr == null) {
@@ -91,31 +82,41 @@
         }
         UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class,
                 context.getApplicationContext(), R.string.user_event_dispatcher_class);
+        ued.mDelegate = delegate;
         ued.mUuidStr = uuidStr;
         ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
         return ued;
     }
 
+    public static UserEventDispatcher newInstance(Context context) {
+        return newInstance(context, null);
+    }
+
+    public interface UserEventDelegate {
+        void modifyUserEvent(LauncherEvent event);
+    }
 
     /**
      * Fills in the container data on the given event if the given view is not null.
      *
      * @return whether container data was added.
      */
-    public boolean fillLogContainer(@Nullable View v, Target child,
-            @Nullable ArrayList<Target> targets) {
-        LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
-        if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
+    public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
+        // Fill in grid(x,y), pageIndex of the child and container type of the parent
+        LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
+        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
             return false;
         }
         final ItemInfo itemInfo = (ItemInfo) v.getTag();
-        firstParent.fillInLogContainerData(itemInfo, child, targets);
+        final Target target = event.srcTarget[0];
+        final Target targetParent = event.srcTarget[1];
+        onFillInLogContainerData(itemInfo, target, targetParent);
+        provider.fillInLogContainerData(v, itemInfo, target, targetParent);
         return true;
     }
 
-    protected void onFillInLogContainerData(@NonNull ItemInfo itemInfo, @NonNull Target target,
-            @NonNull ArrayList<Target> targets) {
-    }
+    protected void onFillInLogContainerData(
+            @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
 
     private boolean mSessionStarted;
     private long mElapsedContainerMillis;
@@ -124,6 +125,7 @@
     private String mUuidStr;
     protected InstantAppResolver mInstantAppResolver;
     private boolean mAppOrTaskLaunch;
+    private UserEventDelegate mDelegate;
     private boolean mPreviousHomeGesture;
 
     //                      APP_ICON    SHORTCUT    WIDGET
@@ -134,15 +136,16 @@
     // --------------------------------------------------------------
 
     @Deprecated
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        Action action = newTouchAction(Action.Touch.TAP);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-        if (fillLogContainer(v, itemTarget, targets)) {
-            onFillInLogContainerData((ItemInfo) v.getTag(), itemTarget, targets);
-            fillIntentInfo(itemTarget, intent, userHandle);
+    public void logAppLaunch(View v, Intent intent, @Nullable  UserHandle userHandle) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
+
+        if (fillInLogContainerData(event, v)) {
+            if (mDelegate != null) {
+                mDelegate.modifyUserEvent(event);
+            }
+            fillIntentInfo(event.srcTarget[0], intent, userHandle);
         }
-        LauncherEvent event = newLauncherEvent(action,  targets);
         ItemInfo info = (ItemInfo) v.getTag();
         if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
@@ -168,7 +171,7 @@
             // Direction DOWN means the task was launched, UP means it was dismissed.
             event.action.dir = direction;
         }
-        event.srcTarget[0].itemType = ItemType.TASK;
+        event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
         event.srcTarget[0].pageIndex = taskIndex;
         fillComponentInfo(event.srcTarget[0], componentKey.componentName);
         dispatchUserEvent(event, null);
@@ -191,11 +194,8 @@
     public void logNotificationLaunch(View v, PendingIntent intent) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
                 newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(v, itemTarget, targets)) {
-            itemTarget.packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
+        if (fillInLogContainerData(event, v)) {
+            event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
         }
         dispatchUserEvent(event, null);
     }
@@ -241,45 +241,50 @@
         LauncherEvent event = newLauncherEvent(newCommandAction(command),
                 newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
-        Target itemTarget = newItemTarget(itemView, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(itemView, itemTarget, targets)) {
+        if (fillInLogContainerData(event, itemView)) {
             // TODO: Remove the following two lines once fillInLogContainerData can take in a
             // container view.
-            itemTarget.type = Target.Type.CONTAINER;
-            itemTarget.containerType = srcContainerType;
+            event.srcTarget[0].type = Target.Type.CONTAINER;
+            event.srcTarget[0].containerType = srcContainerType;
         }
         dispatchUserEvent(event, null);
     }
 
     public void logActionOnControl(int action, int controlType) {
-        logActionOnControl(action, controlType, null);
+        logActionOnControl(action, controlType, null, -1);
     }
 
     public void logActionOnControl(int action, int controlType, int parentContainerType) {
         logActionOnControl(action, controlType, null, parentContainerType);
     }
 
-    /**
-     * Logs control action with proper parent hierarchy
-     */
-    public void logActionOnControl(int actionType, int controlType,
-            @Nullable View controlInContainer, int... parentTypes) {
-        Target control = newTarget(Target.Type.CONTROL);
-        control.controlType = controlType;
-        Action action = newAction(actionType);
+    public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer) {
+        logActionOnControl(action, controlType, controlInContainer, -1);
+    }
 
-        ArrayList<Target> targets = makeTargetsList(control);
+    public void logActionOnControl(int action, int controlType, int parentContainer,
+            int grandParentContainer) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(action),
+                newControlTarget(controlType),
+                newContainerTarget(parentContainer),
+                newContainerTarget(grandParentContainer));
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
+            int parentContainerType) {
+        final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
+                ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
+                : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
+                newTarget(Target.Type.CONTAINER));
+        event.srcTarget[0].controlType = controlType;
         if (controlInContainer != null) {
-            fillLogContainer(controlInContainer, control, targets);
+            fillInLogContainerData(event, controlInContainer);
         }
-        for (int parentContainerType : parentTypes) {
-            if (parentContainerType < 0) continue;
-            targets.add(newContainerTarget(parentContainerType));
+        if (parentContainerType >= 0) {
+            event.srcTarget[1].containerType = parentContainerType;
         }
-        LauncherEvent event = newLauncherEvent(action, targets);
-        if (actionType == Action.Touch.DRAGDROP) {
+        if (action == Action.Touch.DRAGDROP) {
             event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
         }
         dispatchUserEvent(event, null);
@@ -295,7 +300,7 @@
     public void logActionBounceTip(int containerType) {
         LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
                 newContainerTarget(containerType));
-        event.srcTarget[0].tipType = TipType.BOUNCE;
+        event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
         dispatchUserEvent(event, null);
     }
 
@@ -322,7 +327,7 @@
             int srcChildTargetType, int srcParentContainerType, int dstContainerType,
             int pageIndex) {
         LauncherEvent event;
-        if (srcChildTargetType == ItemType.TASK) {
+        if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
             event = newLauncherEvent(newTouchAction(action),
                     newItemTarget(srcChildTargetType),
                     newContainerTarget(srcParentContainerType));
@@ -370,53 +375,52 @@
      * Logs proto lite version of LauncherEvent object to clearcut.
      */
     public void logLauncherEvent(
-            com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
+                com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
 
         if (mPreviousHomeGesture) {
             mPreviousHomeGesture = false;
         }
         mAppOrTaskLaunch = false;
         launcherEvent.toBuilder()
-                .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
-                .setElapsedSessionMillis(
-                        SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
-        try {
-            dispatchUserEvent(LauncherEvent.parseFrom(launcherEvent.toByteArray()), null);
-        } catch (InvalidProtocolBufferNanoException e) {
-            throw new RuntimeException("Cannot convert LauncherEvent from Lite to Nano version.");
+            .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
+            .setElapsedSessionMillis(SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
+        if (!IS_VERBOSE) {
+            return;
         }
+        Log.d(TAG, launcherEvent.toString());
     }
 
     public void logDeepShortcutsOpen(View icon) {
+        LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon);
+        if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) {
+            return;
+        }
         ItemInfo info = (ItemInfo) icon.getTag();
-        Target child = newItemTarget(info, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(child);
-        fillLogContainer(icon, child, targets);
-        dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
+                newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
+        provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
+        dispatchUserEvent(event, null);
+
         resetElapsedContainerMillis("deep shortcut open");
     }
 
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
-        Target srcChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
-        ArrayList<Target> srcTargets = makeTargetsList(srcChild);
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newTarget(Target.Type.CONTAINER));
+        event.destTarget = new Target[]{
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newDropTarget(dropTargetAsView)
+        };
 
+        dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
+                event.srcTarget[0], event.srcTarget[1]);
 
-        Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
-        ArrayList<Target> destTargets = makeTargetsList(destChild);
-
-        dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
         if (dropTargetAsView instanceof LogContainerProvider) {
-            ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
-                    destChild, destTargets);
-        }
-        else {
-            destTargets.add(newDropTarget(dropTargetAsView));
-        }
-        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP), srcTargets);
-        Target[] destTargetsArray = new Target[destTargets.size()];
-        destTargets.toArray(destTargetsArray);
-        event.destTarget = destTargetsArray;
+            ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
+                    dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
 
+        }
         event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
         dispatchUserEvent(event, null);
     }
@@ -428,8 +432,8 @@
         action.command = Action.Command.BACK;
         action.dir = isButton ? Action.Direction.NONE :
                 gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
-                ControlType.BACK_GESTURE);
+        Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
+                LauncherLogProto.ControlType.BACK_GESTURE);
         target.spanX = downX;
         target.spanY = downY;
         target.cardinality = completed ? 1 : 0;
@@ -440,6 +444,8 @@
 
     /**
      * Currently logs following containers: workspace, allapps, widget tray.
+     *
+     * @param reason
      */
     public final void resetElapsedContainerMillis(String reason) {
         mElapsedContainerMillis = SystemClock.uptimeMillis();
@@ -478,23 +484,34 @@
         if (!IS_VERBOSE) {
             return;
         }
-        LauncherLogProto.LauncherEvent liteLauncherEvent;
-        try {
-            liteLauncherEvent =
-                    LauncherLogProto.LauncherEvent.parseFrom(MessageNano.toByteArray(ev));
-        } catch (InvalidProtocolBufferException e) {
-            throw new RuntimeException("Cannot parse LauncherEvent from Nano to Lite version");
-        }
-        Log.d(TAG, liteLauncherEvent.toString());
+        Log.d(TAG, generateLog(ev));
     }
 
     /**
-     * Constructs an ArrayList with targets
+     * Returns a human-readable log for given user event.
      */
-    public static ArrayList<Target> makeTargetsList(Target... targets) {
-        ArrayList<Target> result = new ArrayList<>();
-        for (Target target : targets) {
-            result.add(target);
+    public static String generateLog(LauncherEvent ev) {
+        String log = "\n-----------------------------------------------------"
+                + "\naction:" + LoggerUtils.getActionStr(ev.action);
+        if (ev.srcTarget != null && ev.srcTarget.length > 0) {
+            log += "\n Source " + getTargetsStr(ev.srcTarget);
+        }
+        if (ev.destTarget != null && ev.destTarget.length > 0) {
+            log += "\n Destination " + getTargetsStr(ev.destTarget);
+        }
+        log += String.format(Locale.US,
+                "\n Elapsed container %d ms, session %d ms, action %d ms",
+                ev.elapsedContainerMillis,
+                ev.elapsedSessionMillis,
+                ev.actionDurationMillis);
+        log += "\n\n";
+        return log;
+    }
+
+    private static String getTargetsStr(Target[] targets) {
+        String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
+        for (int i = 1; i < targets.length; i++) {
+            result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
         }
         return result;
     }
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
index e48b8c1..1349eff 100644
--- a/src/com/android/launcher3/model/PagedViewOrientedState.java
+++ b/src/com/android/launcher3/model/PagedViewOrientedState.java
@@ -50,16 +50,7 @@
      */
     private boolean mDisableMultipleOrientations;
 
-    /**
-     * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
-     * @param touchRotation The rotation the nav bar region that is touched is in
-     * @param displayRotation Rotation of the display/device
-     */
     public void update(int touchRotation, int displayRotation) {
-        if (mDisableMultipleOrientations) {
-            return;
-        }
-
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         if (mTouchRotation == Surface.ROTATION_90) {
@@ -71,13 +62,20 @@
         }
     }
 
+    /**
+     * @return {@code true} if the area where the user touched the nav bar is the expected
+     * location for the given display rotation. Ex. bottom of phone in portrait, or left side of
+     * phone in landscape, right side in seascape, etc.
+     * False otherwise
+     */
+    public boolean isTouchRegionNaturalForDisplay() {
+        return mTouchRotation == mDisplayRotation;
+    }
+
     public boolean areMultipleLayoutOrientationsDisabled() {
         return mDisableMultipleOrientations;
     }
 
-    /**
-     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
-     */
     public void disableMultipleOrientations(boolean disable) {
         mDisableMultipleOrientations = disable;
         if (disable) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 18bc55a..1c2acfd 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -240,17 +240,6 @@
      * and align above if there is enough vertical space.
      */
     protected void orientAboutObject() {
-        orientAboutObject(true /* allowAlignLeft */, true /* allowAlignRight */);
-    }
-
-    /**
-     * @see #orientAboutObject()
-     *
-     * @param allowAlignLeft Set to false if we already tried aligning left and didn't have room.
-     * @param allowAlignRight Set to false if we already tried aligning right and didn't have room.
-     * TODO: Can we test this with all permutations of widths/heights and icon locations + RTL?
-     */
-    private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         int width = getMeasuredWidth();
         int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset
@@ -264,8 +253,14 @@
         // Align left (right in RTL) if there is room.
         int leftAlignedX = mTempRect.left;
         int rightAlignedX = mTempRect.right - width;
-        mIsLeftAligned = !mIsRtl ? allowAlignLeft : !allowAlignRight;
-        int x = mIsLeftAligned ? leftAlignedX : rightAlignedX;
+        int x = leftAlignedX;
+        boolean canBeLeftAligned = leftAlignedX + width + insets.left
+                < dragLayer.getRight() - insets.right;
+        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+            x = rightAlignedX;
+        }
+        mIsLeftAligned = x == leftAlignedX;
 
         // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
         int iconWidth = mTempRect.width();
@@ -287,24 +282,6 @@
         }
         x += mIsLeftAligned ? xOffset : -xOffset;
 
-        // Check whether we can still align as we originally wanted, now that we've calculated x.
-        if (!allowAlignLeft && !allowAlignRight) {
-            // We've already tried both ways and couldn't make it fit. onLayout() will set the
-            // gravity to CENTER_HORIZONTAL, but continue below to update y.
-        } else {
-            boolean canBeLeftAligned = x + width + insets.left
-                    < dragLayer.getRight() - insets.right;
-            boolean canBeRightAligned = x > dragLayer.getLeft() + insets.left;
-            boolean alignmentStillValid = mIsLeftAligned && canBeLeftAligned
-                    || !mIsLeftAligned && canBeRightAligned;
-            if (!alignmentStillValid) {
-                // Try again, but don't allow this alignment we already know won't work.
-                orientAboutObject(allowAlignLeft && !mIsLeftAligned /* allowAlignLeft */,
-                        allowAlignRight && mIsLeftAligned /* allowAlignRight */);
-                return;
-            }
-        }
-
         // Open above icon if there is room.
         int iconHeight = mTempRect.height();
         int y = mTempRect.top - height;
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 5af5ebb..445acca 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
@@ -59,6 +58,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
@@ -172,7 +172,7 @@
             BaseDragLayer dl = getPopupContainer();
             if (!dl.isEventOverView(this, ev)) {
                 mLauncher.getUserEventDispatcher().logActionTapOutside(
-                        newContainerTarget(ContainerType.DEEPSHORTCUTS));
+                        LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
                 close(true);
 
                 // We let touches on the original icon go through so that users can launch
@@ -485,15 +485,14 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo == NOTIFICATION_ITEM_INFO) {
-            child.itemType = ItemType.NOTIFICATION;
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        if (info == NOTIFICATION_ITEM_INFO) {
+            target.itemType = ItemType.NOTIFICATION;
         } else {
-            child.itemType = ItemType.DEEPSHORTCUT;
-            child.rank = childInfo.rank;
+            target.itemType = ItemType.DEEPSHORTCUT;
+            target.rank = info.rank;
         }
-        parents.add(newContainerTarget(ContainerType.DEEPSHORTCUTS));
+        targetParent.containerType = ContainerType.DEEPSHORTCUTS;
     }
 
     @Override
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 936d377..8fffee8 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -54,11 +54,6 @@
 
     public SecondaryDragLayer(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
-        recreateControllers();
-    }
-
-    @Override
-    public void recreateControllers() {
         mControllers = new TouchController[] {new CloseAllAppsTouchController()};
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fae0fe2..43d54eb 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,13 +21,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -72,13 +72,26 @@
         return originalSmallestWidth >= 600;
     }
 
+
+    private final ContentObserver mContentObserver =
+        new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                boolean forcedRotation = Utilities.isForcedRotation(mLauncher);
+                PagedView.sFlagForcedRotation = forcedRotation;
+                updateForcedRotation();
+                for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
+                    listener.onForcedRotationChanged(forcedRotation);
+                }
+            }
+        };
+
     public static final int REQUEST_NONE = 0;
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
     private final Launcher mLauncher;
-    private final SharedPreferences mSharedPrefs;
-    private final SharedPreferences mFeatureFlagsPrefs;
+    private final SharedPreferences mPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
     private boolean mAutoRotateEnabled;
@@ -112,42 +125,24 @@
 
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
+        updateForcedRotation();
         if (!mIgnoreAutoRotateSettings) {
-            mSharedPrefs = Utilities.getPrefs(mLauncher);
-            mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
-            mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+            mPrefs = Utilities.getPrefs(mLauncher);
+            mPrefs.registerOnSharedPreferenceChangeListener(this);
+            mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
         } else {
-            mSharedPrefs = null;
+            mPrefs = null;
         }
 
+        // TODO(b/150260456) Add this in home settings as well
         mContentResolver = launcher.getContentResolver();
-        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
-        mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
-        updateForcedRotation(true);
+        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+            FIXED_ROTATION_TRANSFORM_SETTING_NAME), false, mContentObserver);
     }
 
-    /**
-     * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
-     *                          from the home developer settings. Otherwise it will not.
-     *                          This is primarily to allow tests to set their own conditions.
-     */
-    private void updateForcedRotation(boolean setValueFromPrefs) {
-        boolean isForcedRotation = mFeatureFlagsPrefs
-                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
-                && !getAllowRotationDefaultValue();
-        if (mForcedRotation == isForcedRotation) {
-            return;
-        }
-        if (setValueFromPrefs) {
-            mForcedRotation = isForcedRotation;
-        }
-        UI_HELPER_EXECUTOR.execute(
-                () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
-                        mForcedRotation ? 1 : 0));
-        for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
-            listener.onForcedRotationChanged(mForcedRotation);
-        }
+    private void updateForcedRotation() {
+        mForcedRotation = !getAllowRotationDefaultValue() && Utilities.isForcedRotation(mLauncher);
     }
 
     /**
@@ -186,13 +181,8 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
-            updateForcedRotation(true);
-            return;
-        }
-
         boolean wasRotationEnabled = mAutoRotateEnabled;
-        mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+        mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
         if (mAutoRotateEnabled != wasRotationEnabled) {
 
@@ -228,10 +218,6 @@
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
                 allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
-        // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
-        //   Modify tests for this new behavior
-        mForcedRotation = !allowRotation;
-        updateForcedRotation(false);
         notifyChange();
     }
 
@@ -246,11 +232,13 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
-            if (mSharedPrefs != null) {
-                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+            if (mPrefs != null) {
+                mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+            }
+            if (mContentResolver != null) {
+                mContentResolver.unregisterContentObserver(mContentObserver);
             }
             mForcedRotationChangedListeners.clear();
-            mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
         }
     }
 
@@ -305,7 +293,7 @@
         return degrees;
     }
 
-    public static int getRotationFromDegrees(float degrees) {
+    public static int getRotationFromDegrees(int degrees) {
         int threshold = 70;
         if (degrees >= (360 - threshold) || degrees < (threshold)) {
             return Surface.ROTATION_0;
@@ -330,30 +318,11 @@
     }
 
     /**
-     * For landscape, since the navbar is already in a vertical position, we don't have to do any
-     * rotations as the change in Y coordinate is what is read. We only flip the sign of the
-     * y coordinate to make it match existing behavior of swipe to the top to go previous
-     */
-    public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
-        // TODO(b/151269990): Use a temp matrix
-        Matrix m = new Matrix();
-        m.setScale(1, -1);
-        if (inverse) {
-            Matrix inv = new Matrix();
-            m.invert(inv);
-            ev.transform(inv);
-        } else {
-            ev.transform(m);
-        }
-    }
-
-    /**
      * Creates a matrix to transform the given motion event specified by degrees.
      * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
      */
     public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
         Matrix transform = new Matrix();
-        // TODO(b/151269990): Use a temp matrix
         transform.setRotate(degrees);
         if (inverse) {
             Matrix inv = new Matrix();
@@ -375,7 +344,6 @@
      */
     public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
         Matrix m = new Matrix();
-        // TODO(b/151269990): Use a temp matrix
         switch (displayRotation) {
             case Surface.ROTATION_0:
                 return m;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c3664c3..34d69e9 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,9 +19,9 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
-import static com.android.launcher3.LauncherStateManager.PLAY_NON_ATOMIC;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
@@ -37,16 +37,16 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.FlingBlockCheck;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.TouchController;
 
 /**
@@ -176,7 +176,7 @@
     protected abstract LauncherState getTargetState(LauncherState fromState,
             boolean isDragTowardPositive);
 
-    protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
+    protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
 
     /**
      * Returns the container that the touch started from when leaving NORMAL state.
@@ -201,10 +201,10 @@
             mCurrentAnimation.setOnCancelRunnable(null);
         }
         int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
-                ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
+                ? NON_ATOMIC_COMPONENT : ANIM_ALL;
         mScheduleResumeAtomicComponent = false;
         if (mAtomicAnim != null) {
-            animComponents = PLAY_NON_ATOMIC;
+            animComponents = NON_ATOMIC_COMPONENT;
             // Control the non-atomic components until the atomic animation finishes, then control
             // the atomic components as well.
             mScheduleResumeAtomicComponent = true;
@@ -215,7 +215,7 @@
         }
 
         if (mAtomicComponentsController != null) {
-            animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE;
+            animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
         }
         mProgressMultiplier = initCurrentAnimation(animComponents);
         mCurrentAnimation.dispatchOnStart();
@@ -360,7 +360,7 @@
             long duration) {
         AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
         return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
-                PLAY_ATOMIC_OVERVIEW_SCALE, duration);
+                ATOMIC_OVERVIEW_SCALE_COMPONENT, duration);
     }
 
     protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
@@ -434,7 +434,7 @@
         maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
         updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
                 targetState, velocity, fling);
-        mCurrentAnimation.dispatchOnStart();
+        mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity);
         if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 8d5f33d..31a5d79 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -76,7 +76,7 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationFlags int animComponents) {
+    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager()
diff --git a/src/com/android/launcher3/util/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java
new file mode 100644
index 0000000..617a38b
--- /dev/null
+++ b/src/com/android/launcher3/util/PendingAnimation.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import android.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to keep track of a running animation.
+ *
+ * This class allows attaching end callbacks to an animation is intended to be used with
+ * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
+ * AnimationListeners are not properly dispatched.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class PendingAnimation {
+
+    private final ArrayList<Consumer<OnEndListener>> mEndListeners = new ArrayList<>();
+
+    public final AnimatorSet anim;
+
+    public PendingAnimation(AnimatorSet anim) {
+        this.anim = anim;
+    }
+
+    public void finish(boolean isSuccess, int logAction) {
+        for (Consumer<OnEndListener> listeners : mEndListeners) {
+            listeners.accept(new OnEndListener(isSuccess, logAction));
+        }
+        mEndListeners.clear();
+    }
+
+    public void addEndListener(Consumer<OnEndListener> listener) {
+        mEndListeners.add(listener);
+    }
+
+    public static class OnEndListener {
+        public boolean isSuccess;
+        public int logAction;
+
+        public OnEndListener(boolean isSuccess, int logAction) {
+            this.isSuccess = isSuccess;
+            this.logAction = logAction;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 25748ae..254655c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -40,6 +40,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -115,11 +116,6 @@
     }
 
     /**
-     * Called to reinitialize touch controllers.
-     */
-    public abstract void recreateControllers();
-
-    /**
      * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
      */
     public boolean isEventOverView(View view, MotionEvent ev) {
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 6d204f6..8ce98f2 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -40,7 +40,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
-import android.util.IntProperty;
+import android.util.Property;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -80,8 +80,8 @@
 public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
         AccessibilityStateChangeListener, StateListener {
 
-    public static final IntProperty<ScrimView> DRAG_HANDLE_ALPHA =
-            new IntProperty<ScrimView>("dragHandleAlpha") {
+    public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA =
+            new Property<ScrimView, Integer>(Integer.TYPE, "dragHandleAlpha") {
 
                 @Override
                 public Integer get(ScrimView scrimView) {
@@ -89,7 +89,7 @@
                 }
 
                 @Override
-                public void setValue(ScrimView scrimView, int value) {
+                public void set(ScrimView scrimView, Integer value) {
                     scrimView.setDragHandleAlpha(value);
                 }
             };
@@ -336,7 +336,7 @@
     }
 
     private void updateDragHandleVisibility(Drawable recycle) {
-        boolean visible = shouldDragHandleBeVisible();
+        boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
         boolean wasVisible = mDragHandle != null;
         if (visible != wasVisible) {
             if (visible) {
@@ -352,10 +352,6 @@
         }
     }
 
-    protected boolean shouldDragHandleBeVisible() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
-    }
-
     @Override
     public boolean dispatchHoverEvent(MotionEvent event) {
         return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index d849138..81f8327 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -46,8 +46,7 @@
 public class WorkEduView extends AbstractSlideInView implements Insettable {
 
     private static final int DEFAULT_CLOSE_DURATION = 200;
-    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
-    public static final String KEY_LEGACY_WORK_EDU_SEEN = "showed_bottom_user_education";
+    private static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
 
     private static final int WORK_EDU_NOT_STARTED = 0;
     private static final int WORK_EDU_PERSONAL_APPS = 1;
@@ -103,8 +102,6 @@
         mProceedButton = findViewById(R.id.proceed);
         mContentText = findViewById(R.id.content_text);
 
-        // make sure layout does not shrink when we change the text
-        mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
         if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
             mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
         }
@@ -182,8 +179,8 @@
         if (oldListener != null) {
             launcher.getStateManager().removeStateListener(oldListener);
         }
-        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
-                WORK_EDU_NOT_STARTED) != WORK_EDU_NOT_STARTED) {
+        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+                != WORK_EDU_NOT_STARTED) {
             return null;
         }
 
@@ -213,8 +210,8 @@
      * Shows work apps edu if user had dismissed full edu flow
      */
     public static void showWorkEduIfNeeded(Launcher launcher) {
-        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
-                WORK_EDU_NOT_STARTED) != WORK_EDU_PERSONAL_APPS) {
+        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+                != WORK_EDU_PERSONAL_APPS) {
             return;
         }
         LayoutInflater layoutInflater = LayoutInflater.from(launcher);
@@ -223,8 +220,4 @@
         v.show();
         v.goToWorkTab(false);
     }
-
-    private static boolean hasSeenLegacyEdu(Launcher launcher) {
-        return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
-    }
 }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 73a0615..df1a469 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -41,8 +41,6 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AbstractSlideInView;
 
-import java.util.ArrayList;
-
 /**
  * Base class for various widgets popup
  */
@@ -146,11 +144,9 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        Target target = newContainerTarget(ContainerType.WIDGETS);
-        target.cardinality = getElementsRowCount();
-        parents.add(target);
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        targetParent.containerType = ContainerType.WIDGETS;
+        targetParent.cardinality = getElementsRowCount();
     }
 
     @Override