Moving animation configuration and definition out of state definition

This removes cross dependency between StateManager and State object so
that it can be easily generalized

Change-Id: I62851fc4b653655cb40f37023db9651055ec7c9c
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index a515070..c037e44 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,39 +16,24 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.states.StateAnimationConfig;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -59,13 +44,6 @@
  */
 public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
 
-    public static final int INDEX_SHELF_ANIM = 0;
-    public static final int INDEX_RECENTS_FADE_ANIM = 1;
-    public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
-    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
-
-    public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
-
     public LauncherAppTransitionManagerImpl(Context context) {
         super(context);
     }
@@ -151,77 +129,4 @@
             mLauncher.getStateManager().reapplyState();
         };
     }
-
-    @Override
-    public int getStateElementAnimationsCount() {
-        return 4;
-    }
-
-    @Override
-    public Animator createStateElementAnimation(int index, float... values) {
-        switch (index) {
-            case INDEX_SHELF_ANIM: {
-                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
-                Animator springAnim = aatc.createSpringAnimation(values);
-
-                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                    // Translate hotseat with the shelf until reaching overview.
-                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
-                    float shiftRange = aatc.getShiftRange();
-                    if (values.length == 1) {
-                        values = new float[] {aatc.getProgress(), values[0]};
-                    }
-                    ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
-                    hotseatAnim.addUpdateListener(anim -> {
-                        float progress = (Float) anim.getAnimatedValue();
-                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
-                            float hotseatShift = (progress - overviewProgress) * shiftRange;
-                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
-                        }
-                    });
-                    hotseatAnim.setInterpolator(LINEAR);
-                    hotseatAnim.setDuration(springAnim.getDuration());
-
-                    AnimatorSet anim = new AnimatorSet();
-                    anim.play(hotseatAnim);
-                    anim.play(springAnim);
-                    return anim;
-                }
-
-                return springAnim;
-            }
-            case INDEX_RECENTS_FADE_ANIM:
-                return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
-                        RecentsView.CONTENT_ALPHA, values);
-            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
-                RecentsView rv = mLauncher.getOverviewPanel();
-                return new SpringAnimationBuilder(mLauncher)
-                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
-                        .setDampingRatio(0.8f)
-                        .setStiffness(250)
-                        .setValues(values)
-                        .build(rv, ADJACENT_PAGE_OFFSET);
-            }
-            case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
-                StateAnimationConfig config = new StateAnimationConfig();
-                config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
-
-                config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-                config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
-                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                    config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
-                    config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
-                }
-
-
-                LauncherStateManager stateManager = mLauncher.getStateManager();
-                return stateManager.createAtomicAnimation(
-                        stateManager.getCurrentStableState(), OVERVIEW, config);
-            }
-
-            default:
-                return super.createStateElementAnimation(index, values);
-        }
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index eaf9f61..1876424 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AtomicAnimationFactory;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -48,6 +49,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
@@ -278,6 +280,11 @@
         return list.toArray(new TouchController[list.size()]);
     }
 
+    @Override
+    public AtomicAnimationFactory createAtomicAnimationFactory() {
+        return new QuickstepAtomicAnimationFactory(this);
+    }
+
     private static final class LauncherTaskViewController extends
             TaskViewTouchController<Launcher> {
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index b27f16a..fc9a11b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -15,16 +15,7 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.states.StateAnimationConfig;
 
 public class OverviewPeekState extends OverviewState {
     private static final float OVERVIEW_OFFSET = 0.7f;
@@ -37,14 +28,4 @@
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
         return new float[] {NO_SCALE, OVERVIEW_OFFSET};
     }
-
-    @Override
-    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            StateAnimationConfig config) {
-        if (this == OVERVIEW_PEEK && fromState == NORMAL) {
-            config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
-            config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
-        }
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6f57281..9f31608 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,36 +15,21 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -57,9 +42,6 @@
  */
 public class OverviewState extends LauncherState {
 
-    // Scale recents takes before animating in
-    private static final float RECENTS_PREPARE_SCALE = 1.33f;
-
     protected static final Rect sTempRect = new Rect();
 
     private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
@@ -202,35 +184,6 @@
         }
     }
 
-    @Override
-    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            StateAnimationConfig config) {
-        if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
-            if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
-                config.setInterpolator(ANIM_WORKSPACE_SCALE,
-                        fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
-                config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
-            } else {
-                config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
-
-                // Scale up the recents, if it is not coming from the side
-                RecentsView overview = launcher.getOverviewPanel();
-                if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
-                    SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
-                }
-            }
-            config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
-            config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
-            Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
-                    && removeShelfFromOverview(launcher)
-                    ? OVERSHOOT_1_2
-                    : OVERSHOOT_1_7;
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
-        }
-    }
-
     public static OverviewState newBackgroundState(int id) {
         return new BackgroundAppState(id);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
new file mode 100644
index 0000000..6bc69f9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -0,0 +1,236 @@
+/*
+ * 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.uioverrides.states;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AtomicAnimationFactory;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Animation factory for quickstep specific transitions
+ */
+public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory {
+
+    // Scale recents takes before animating in
+    private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
+    public static final int INDEX_SHELF_ANIM = 0;
+    public static final int INDEX_RECENTS_FADE_ANIM = 1;
+    public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
+    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
+    private static final int ANIM_COUNT = 4;
+
+    public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
+
+    private final QuickstepLauncher mLauncher;
+
+    public QuickstepAtomicAnimationFactory(QuickstepLauncher launcher) {
+        super(ANIM_COUNT);
+        mLauncher = launcher;
+    }
+
+    @Override
+    public Animator createStateElementAnimation(int index, float... values) {
+        switch (index) {
+            case INDEX_SHELF_ANIM: {
+                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+                Animator springAnim = aatc.createSpringAnimation(values);
+
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    // Translate hotseat with the shelf until reaching overview.
+                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+                    float shiftRange = aatc.getShiftRange();
+                    if (values.length == 1) {
+                        values = new float[] {aatc.getProgress(), values[0]};
+                    }
+                    ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+                    hotseatAnim.addUpdateListener(anim -> {
+                        float progress = (Float) anim.getAnimatedValue();
+                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                            float hotseatShift = (progress - overviewProgress) * shiftRange;
+                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+                        }
+                    });
+                    hotseatAnim.setInterpolator(LINEAR);
+                    hotseatAnim.setDuration(springAnim.getDuration());
+
+                    AnimatorSet anim = new AnimatorSet();
+                    anim.play(hotseatAnim);
+                    anim.play(springAnim);
+                    return anim;
+                }
+
+                return springAnim;
+            }
+            case INDEX_RECENTS_FADE_ANIM:
+                return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
+                        RecentsView.CONTENT_ALPHA, values);
+            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+                RecentsView rv = mLauncher.getOverviewPanel();
+                return new SpringAnimationBuilder(mLauncher)
+                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
+                        .setDampingRatio(0.8f)
+                        .setStiffness(250)
+                        .setValues(values)
+                        .build(rv, ADJACENT_PAGE_OFFSET);
+            }
+            case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+                StateAnimationConfig config = new StateAnimationConfig();
+                config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+                    config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+                }
+
+                LauncherStateManager stateManager = mLauncher.getStateManager();
+                return stateManager.createAtomicAnimation(
+                        stateManager.getCurrentStableState(), OVERVIEW, config);
+            }
+
+            default:
+                return super.createStateElementAnimation(index, values);
+        }
+    }
+
+    @Override
+    public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
+            StateAnimationConfig config) {
+        if (toState == NORMAL && fromState == OVERVIEW) {
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+            config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+            config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+            Workspace workspace = mLauncher.getWorkspace();
+
+            // Start from a higher workspace scale, but only if we're invisible so we don't jump.
+            boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
+            if (isWorkspaceVisible) {
+                CellLayout currentChild = (CellLayout) workspace.getChildAt(
+                        workspace.getCurrentPage());
+                isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
+                        && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
+            }
+            if (!isWorkspaceVisible) {
+                workspace.setScaleX(0.92f);
+                workspace.setScaleY(0.92f);
+            }
+            Hotseat hotseat = mLauncher.getHotseat();
+            boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
+            if (!isHotseatVisible) {
+                hotseat.setScaleX(0.92f);
+                hotseat.setScaleY(0.92f);
+                if (ENABLE_OVERVIEW_ACTIONS.get()) {
+                    AllAppsContainerView qsbContainer = mLauncher.getAppsView();
+                    View qsb = qsbContainer.getSearchView();
+                    boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+                    if (!qsbVisible) {
+                        qsbContainer.setScaleX(0.92f);
+                        qsbContainer.setScaleY(0.92f);
+                    }
+                }
+            }
+        } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
+            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
+            config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
+        } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
+            config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
+        } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
+            if (SysUINavigationMode.getMode(mLauncher) == NO_BUTTON) {
+                config.setInterpolator(ANIM_WORKSPACE_SCALE,
+                        fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+            } else {
+                config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+
+                // Scale up the recents, if it is not coming from the side
+                RecentsView overview = mLauncher.getOverviewPanel();
+                if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+                    SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+                }
+            }
+            config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+            Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+                    && removeShelfFromOverview(mLauncher)
+                    ? OVERSHOOT_1_2
+                    : OVERSHOOT_1_7;
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 8af2747..05dd797 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -31,6 +30,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -42,12 +42,12 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
@@ -82,7 +82,7 @@
 
     @Override
     protected long getAtomicDuration() {
-        return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+        return QuickstepAtomicAnimationFactory.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
     }
 
     @Override
@@ -206,8 +206,8 @@
             mPeekAnim.cancel();
         }
 
-        Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
-                INDEX_PAUSE_TO_OVERVIEW_ANIM);
+        Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+                .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
         mAtomicAnim = new AnimatorSet();
         mAtomicAnim.addListener(new AnimationSuccessListener() {
             @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index f4f8bc9..7385658 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -35,6 +34,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
@@ -312,8 +312,8 @@
         if (mMotionPauseDetector.isPaused() && noFling) {
             cancelAnimations();
 
-            Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
-                    INDEX_PAUSE_TO_OVERVIEW_ANIM);
+            Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+                    .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
             overviewAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 4c2bd1b..5e688fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -16,15 +16,15 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_FADE_ANIM;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_TRANSLATE_X_ANIM;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
 import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
index 217eca5..85006da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -15,10 +15,10 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1d9c0c3..873b066 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -92,6 +92,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.LauncherStateManager.AtomicAnimationFactory;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -2724,6 +2725,13 @@
         return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
+    /**
+     * Creates a factory for atomic state animations
+     */
+    public AtomicAnimationFactory createAtomicAnimationFactory() {
+        return new AtomicAnimationFactory(0);
+    }
+
     public TouchController[] createTouchControllers() {
         return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
     }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 9148c2f..24e0d14 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -13,11 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.launcher3;
 
-
-import android.animation.Animator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Rect;
@@ -58,17 +55,6 @@
     }
 
     /**
-     * Number of animations which run on state properties.
-     */
-    public int getStateElementAnimationsCount() {
-        return 0;
-    }
-
-    public Animator createStateElementAnimation(int index, float... values) {
-        throw new RuntimeException("Unknown gesture animation " + index);
-    }
-
-    /**
      * Registers remote animations for certain system transitions.
      */
     public void registerRemoteAnimations() {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 686a241..e133d31 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -15,19 +15,7 @@
  */
 package com.android.launcher3;
 
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
@@ -39,20 +27,16 @@
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
 import android.content.Context;
-import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
-import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
 
-
 /**
  * Base state for various states used for the Launcher
  */
@@ -293,55 +277,6 @@
         }
     }
 
-    /**
-     * Prepares for a non-user controlled animation from fromState to this state. Preparations
-     * include:
-     * - Setting interpolators for various animations included in the state transition.
-     * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
-     */
-    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            StateAnimationConfig config) {
-        if (this == NORMAL && fromState == OVERVIEW) {
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
-            config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
-            config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-            Workspace workspace = launcher.getWorkspace();
-
-            // Start from a higher workspace scale, but only if we're invisible so we don't jump.
-            boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
-            if (isWorkspaceVisible) {
-                CellLayout currentChild = (CellLayout) workspace.getChildAt(
-                        workspace.getCurrentPage());
-                isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
-                        && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
-            }
-            if (!isWorkspaceVisible) {
-                workspace.setScaleX(0.92f);
-                workspace.setScaleY(0.92f);
-            }
-            Hotseat hotseat = launcher.getHotseat();
-            boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
-            if (!isHotseatVisible) {
-                hotseat.setScaleX(0.92f);
-                hotseat.setScaleY(0.92f);
-                if (ENABLE_OVERVIEW_ACTIONS.get()) {
-                    AllAppsContainerView qsbContainer = launcher.getAppsView();
-                    View qsb = qsbContainer.getSearchView();
-                    boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
-                    if (!qsbVisible) {
-                        qsbContainer.setScaleX(0.92f);
-                        qsbContainer.setScaleY(0.92f);
-                    }
-                }
-            }
-        } else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
-            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
-        }
-    }
-
     public static abstract class PageAlphaProvider {
 
         public final Interpolator interpolator;
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index ea41fc4..1aae201 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -86,7 +86,7 @@
     private final ArrayList<StateListener> mListeners = new ArrayList<>();
 
     // Animators which are run on properties also controlled by state animations.
-    private Animator[] mStateElementAnimators;
+    private final AtomicAnimationFactory mAtomicAnimationFactory;
 
     private StateHandler[] mStateHandlers;
     private LauncherState mState = NORMAL;
@@ -99,6 +99,8 @@
     public LauncherStateManager(Launcher l) {
         mUiHandler = new Handler(Looper.getMainLooper());
         mLauncher = l;
+
+        mAtomicAnimationFactory = l.createAtomicAnimationFactory();
     }
 
     public LauncherState getState() {
@@ -195,7 +197,7 @@
     public void reapplyState(boolean cancelCurrentAnimation) {
         boolean wasInAnimation = mConfig.currentAnimation != null;
         if (cancelCurrentAnimation) {
-            cancelAllStateElementAnimation();
+            mAtomicAnimationFactory.cancelAllStateElementAnimation();
             cancelAnimation();
         }
         if (mConfig.currentAnimation == null) {
@@ -233,7 +235,7 @@
         mConfig.reset();
 
         if (!animated) {
-            cancelAllStateElementAnimation();
+            mAtomicAnimationFactory.cancelAllStateElementAnimation();
             onStateTransitionStart(state);
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(state);
@@ -284,7 +286,7 @@
      */
     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
             StateAnimationConfig config) {
-        toState.prepareForAtomicAnimation(mLauncher, fromState, config);
+        mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
     }
 
     /**
@@ -447,42 +449,23 @@
         mConfig.setAnimation(anim, null);
     }
 
-    private void cancelAllStateElementAnimation() {
-        if (mStateElementAnimators == null) {
-            return;
-        }
-
-        for (Animator animator : mStateElementAnimators) {
-            if (animator != null) {
-                animator.cancel();
-            }
-        }
-    }
-
     /**
      * Cancels a currently running gesture animation
      */
     public void cancelStateElementAnimation(int index) {
-        if (mStateElementAnimators == null) {
-            return;
-        }
-        if (mStateElementAnimators[index] != null) {
-            mStateElementAnimators[index].cancel();
+        if (mAtomicAnimationFactory.mStateElementAnimators[index] != null) {
+            mAtomicAnimationFactory.mStateElementAnimators[index].cancel();
         }
     }
 
     public Animator createStateElementAnimation(int index, float... values) {
         cancelStateElementAnimation(index);
-        LauncherAppTransitionManager latm = mLauncher.getAppTransitionManager();
-        if (mStateElementAnimators == null) {
-            mStateElementAnimators = new Animator[latm.getStateElementAnimationsCount()];
-        }
-        Animator anim = latm.createStateElementAnimation(index, values);
-        mStateElementAnimators[index] = anim;
+        Animator anim = mAtomicAnimationFactory.createStateElementAnimation(index, values);
+        mAtomicAnimationFactory.mStateElementAnimators[index] = anim;
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mStateElementAnimators[index] = null;
+                mAtomicAnimationFactory.mStateElementAnimators[index] = null;
             }
         });
         return anim;
@@ -590,4 +573,46 @@
 
         default void onStateTransitionComplete(LauncherState finalState) { }
     }
+
+    /**
+     * Factory class to configure and create atomic animations.
+     */
+    public static class AtomicAnimationFactory {
+
+        private final Animator[] mStateElementAnimators;
+
+        /**
+         *
+         * @param sharedElementAnimCount number of animations which run on state properties
+         */
+        public AtomicAnimationFactory(int sharedElementAnimCount) {
+            mStateElementAnimators = new Animator[sharedElementAnimCount];
+        }
+
+        void cancelAllStateElementAnimation() {
+            for (Animator animator : mStateElementAnimators) {
+                if (animator != null) {
+                    animator.cancel();
+                }
+            }
+        }
+
+        /**
+         * Creates animations for elements which can be also be part of state transitions. The
+         * actual definition of the animation is up to the app to define.
+         *
+         */
+        public Animator createStateElementAnimation(int index, float... values) {
+            throw new RuntimeException("Unknown gesture animation " + index);
+        }
+
+        /**
+         * Prepares for a non-user controlled animation from fromState to this state. Preparations
+         * include:
+         * - Setting interpolators for various animations included in the state transition.
+         * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
+         */
+        public void prepareForAtomicAnimation(
+                LauncherState fromState, LauncherState toState, StateAnimationConfig config) { }
+    }
 }