Merge "Add taskbar icons unfold animation" into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 033cf8e..2534699 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,8 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.ServiceConnection;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -73,6 +75,8 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -82,6 +86,9 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldTransitionFactory;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 
 import java.util.List;
 import java.util.stream.Stream;
@@ -117,6 +124,7 @@
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
             mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+
             Log.d(TAG, "TIS service connected");
             resetServiceBindRetryState();
 
@@ -142,16 +150,41 @@
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private SplitPlaceholderView mSplitPlaceholderView;
 
+    private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
         addMultiWindowModeChangedListener(mDepthController);
+        initUnfoldTransitionProgressProvider();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onResume();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onPause();
+        }
+
+        super.onPause();
     }
 
     @Override
     public void onDestroy() {
         mAppTransitionManager.onActivityDestroyed();
+        if (mUnfoldTransitionProgressProvider != null) {
+            mUnfoldTransitionProgressProvider.destroy();
+        }
 
         SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
 
@@ -160,6 +193,11 @@
             mTaskbarManager.clearLauncher(this);
         }
         resetServiceBindRetryState();
+
+        if (mLauncherUnfoldAnimationController != null) {
+            mLauncherUnfoldAnimationController.onDestroy();
+        }
+
         super.onDestroy();
     }
 
@@ -334,6 +372,28 @@
         mConnectionAttempts = 0;
     }
 
+    private void initUnfoldTransitionProgressProvider() {
+        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+        if (config.isEnabled()) {
+            mUnfoldTransitionProgressProvider =
+                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+                            this,
+                            config,
+                            ProxyScreenStatusProvider.INSTANCE,
+                            getSystemService(DeviceStateManager.class),
+                            getSystemService(SensorManager.class),
+                            getMainThreadHandler(),
+                            getMainExecutor()
+                    );
+
+            mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+                    this,
+                    getWindowManager(),
+                    mUnfoldTransitionProgressProvider
+            );
+        }
+    }
+
     public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
         mTaskbarUIController = taskbarUIController;
     }
@@ -373,6 +433,11 @@
         return mTaskbarStateHandler;
     }
 
+    @Nullable
+    public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+        return mUnfoldTransitionProgressProvider;
+    }
+
     @Override
     public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8c12567..51d031b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -98,7 +99,8 @@
     private boolean mIsDestroyed = false;
 
     public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
-            TaskbarNavButtonController buttonController) {
+            TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
+            unfoldTransitionProgressProvider) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
         mDeviceProfile = dp;
 
@@ -120,6 +122,14 @@
         FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
         StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
 
+        Display display = windowContext.getDisplay();
+        Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
+                ? windowContext.getApplicationContext()
+                : windowContext.getApplicationContext().createDisplayContext(display);
+        mWindowManager = c.getSystemService(WindowManager.class);
+        mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+
         // Construct controllers.
         mControllers = new TaskbarControllers(this,
                 new TaskbarDragController(this),
@@ -129,18 +139,12 @@
                         R.color.popup_color_primary_light),
                 new TaskbarDragLayerController(this, mDragLayer),
                 new TaskbarViewController(this, taskbarView),
+                new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider,
+                        mWindowManager),
                 new TaskbarKeyguardController(this),
                 new StashedHandleViewController(this, stashedHandleView),
                 new TaskbarStashController(this),
                 new TaskbarEduController(this));
-
-        Display display = windowContext.getDisplay();
-        Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
-                ? windowContext.getApplicationContext()
-                : windowContext.getApplicationContext().createDisplayContext(display);
-        mWindowManager = c.getSystemService(WindowManager.class);
-        mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
     }
 
     public void init() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 1197543..6144881 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -31,6 +31,7 @@
     public final RotationButtonController rotationButtonController;
     public final TaskbarDragLayerController taskbarDragLayerController;
     public final TaskbarViewController taskbarViewController;
+    public final TaskbarUnfoldAnimationController taskbarUnfoldAnimationController;
     public final TaskbarKeyguardController taskbarKeyguardController;
     public final StashedHandleViewController stashedHandleViewController;
     public final TaskbarStashController taskbarStashController;
@@ -46,6 +47,7 @@
             RotationButtonController rotationButtonController,
             TaskbarDragLayerController taskbarDragLayerController,
             TaskbarViewController taskbarViewController,
+            TaskbarUnfoldAnimationController taskbarUnfoldAnimationController,
             TaskbarKeyguardController taskbarKeyguardController,
             StashedHandleViewController stashedHandleViewController,
             TaskbarStashController taskbarStashController,
@@ -57,6 +59,7 @@
         this.rotationButtonController = rotationButtonController;
         this.taskbarDragLayerController = taskbarDragLayerController;
         this.taskbarViewController = taskbarViewController;
+        this.taskbarUnfoldAnimationController = taskbarUnfoldAnimationController;
         this.taskbarKeyguardController = taskbarKeyguardController;
         this.stashedHandleViewController = stashedHandleViewController;
         this.taskbarStashController = taskbarStashController;
@@ -75,6 +78,7 @@
         }
         taskbarDragLayerController.init(this);
         taskbarViewController.init(this);
+        taskbarUnfoldAnimationController.init(this);
         taskbarKeyguardController.init(navbarButtonsViewController);
         stashedHandleViewController.init(this);
         taskbarStashController.init(this);
@@ -89,6 +93,7 @@
         rotationButtonController.onDestroy();
         taskbarDragLayerController.onDestroy();
         taskbarKeyguardController.onDestroy();
+        taskbarUnfoldAnimationController.onDestroy();
         taskbarViewController.onDestroy();
         stashedHandleViewController.onDestroy();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 4ed83f2..ec98bbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -42,6 +42,7 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
 
 /**
  * Class to manage taskbar lifecycle
@@ -58,6 +59,10 @@
     private final TaskbarNavButtonController mNavButtonController;
     private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
 
+    // The source for this provider is set when Launcher is available
+    private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
+            new ScopedUnfoldTransitionProgressProvider();
+
     private TaskbarActivityContext mTaskbarActivityContext;
     private BaseQuickstepLauncher mLauncher;
     /**
@@ -120,6 +125,9 @@
      */
     public void setLauncher(@NonNull BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
+        mUnfoldProgressProvider.setSourceProvider(launcher
+                .getUnfoldTransitionProgressProvider());
+
         if (mTaskbarActivityContext != null) {
             mTaskbarActivityContext.setUIController(
                     new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
@@ -135,6 +143,7 @@
             if (mTaskbarActivityContext != null) {
                 mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
             }
+            mUnfoldProgressProvider.setSourceProvider(null);
         }
     }
 
@@ -153,8 +162,8 @@
             return;
         }
 
-        mTaskbarActivityContext = new TaskbarActivityContext(
-                mContext, dp.copy(mContext), mNavButtonController);
+        mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
+                mNavButtonController, mUnfoldProgressProvider);
         mTaskbarActivityContext.init();
         if (mLauncher != null) {
             mTaskbarActivityContext.setUIController(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
new file mode 100644
index 0000000..43f015c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+/**
+ * Controls animation of taskbar icons when unfolding foldable devices
+ */
+public class TaskbarUnfoldAnimationController {
+
+    private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
+    private final TransitionListener mTransitionListener = new TransitionListener();
+    private TaskbarViewController mTaskbarViewController;
+
+    public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
+            unfoldTransitionProgressProvider, WindowManager windowManager) {
+        mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+        mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
+                new LauncherViewsMoveFromCenterTranslationApplier());
+    }
+
+    /**
+     * Initializes the controller
+     * @param taskbarControllers references to all other taskbar controllers
+     */
+    public void init(TaskbarControllers taskbarControllers) {
+        mTaskbarViewController = taskbarControllers.taskbarViewController;
+        mTaskbarViewController.addOneTimePreDrawListener(() ->
+                mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+        mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+    }
+
+    /**
+     * Destroys the controller
+     */
+    public void onDestroy() {
+        mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
+        mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+    }
+
+    private class TransitionListener implements TransitionProgressListener {
+
+        @Override
+        public void onTransitionStarted() {
+            mMoveFromCenterAnimator.updateDisplayProperties();
+            View[] icons = mTaskbarViewController.getIconViews();
+            for (View icon : icons) {
+                // TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
+                //                   during the animation
+                mMoveFromCenterAnimator.registerViewForAnimation(icon);
+            }
+
+            mMoveFromCenterAnimator.onTransitionStarted();
+        }
+
+        @Override
+        public void onTransitionFinished() {
+            mMoveFromCenterAnimator.onTransitionFinished();
+        }
+
+        @Override
+        public void onTransitionProgress(float progress) {
+            mMoveFromCenterAnimator.onTransitionProgress(progress);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index f1748af..f359a3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,20 +16,24 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.AnimatedFloat;
@@ -117,6 +121,25 @@
         mTaskbarView.setClickAndLongClickListenersForIcon(icon);
     }
 
+    /**
+     * Adds one time pre draw listener to the taskbar view, it is called before
+     * drawing a frame and invoked only once
+     * @param listener callback that will be invoked before drawing the next frame
+     */
+    public void addOneTimePreDrawListener(Runnable listener) {
+        mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
+                if (viewTreeObserver.isAlive()) {
+                    listener.run();
+                    viewTreeObserver.removeOnPreDrawListener(this);
+                }
+                return true;
+            }
+        });
+    }
+
     public Rect getIconLayoutBounds() {
         return mTaskbarView.getIconLayoutBounds();
     }
@@ -194,7 +217,7 @@
             float childCenter = (child.getLeft() + child.getRight()) / 2;
             float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
                     + hotseatCellSize / 2;
-            setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+            setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
         }
 
         AnimatorPlaybackController controller = setter.createPlaybackController();
@@ -257,4 +280,30 @@
             return false;
         }
     }
+
+    public static final FloatProperty<View> ICON_TRANSLATE_X =
+            new FloatProperty<View>("taskbarAligmentTranslateX") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+                    } else {
+                        view.setTranslationX(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view)
+                                .getTranslationXForTaskbarAlignmentAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
+                    }
+                    return view.getTranslationX();
+                }
+            };
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 14bc380..2009cd7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -36,13 +36,9 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -79,14 +75,9 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.LauncherUnfoldAnimationController;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.unfold.UnfoldTransitionFactory;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -106,51 +97,10 @@
     private FixedContainerItems mAllAppsPredictions;
     private HotseatPredictionController mHotseatPredictionController;
 
-    @Nullable
-    private LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
-
     @Override
     protected void setupViews() {
         super.setupViews();
         mHotseatPredictionController = new HotseatPredictionController(this);
-
-        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
-        if (config.isEnabled()) {
-            final UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
-                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
-                            this,
-                            config,
-                            ProxyScreenStatusProvider.INSTANCE,
-                            getSystemService(DeviceStateManager.class),
-                            getSystemService(SensorManager.class),
-                            getMainThreadHandler(),
-                            getMainExecutor()
-                    );
-
-            mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
-                    this,
-                    getWindowManager(),
-                    unfoldTransitionProgressProvider
-            );
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onResume();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onPause();
-        }
-
-        super.onPause();
     }
 
     @Override
@@ -281,10 +231,6 @@
     public void onDestroy() {
         super.onDestroy();
         mHotseatPredictionController.destroy();
-
-        if (mLauncherUnfoldAnimationController != null) {
-            mLauncherUnfoldAnimationController.onDestroy();
-        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index c5ab84d..47d3580 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -37,26 +37,23 @@
     private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
 
     private final Launcher mLauncher;
-    private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
-    private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation;
 
     @Nullable
     private HorizontalInsettableView mQsbInsettable;
 
-    private final AnimationListener mAnimationListener = new AnimationListener();
-
-    private boolean mIsTransitionRunning = false;
-    private boolean mIsReadyToPlayAnimation = false;
+    private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
 
     public LauncherUnfoldAnimationController(
             Launcher launcher,
             WindowManager windowManager,
             UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
         mLauncher = launcher;
-        mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
-        mMoveFromCenterWorkspaceAnimation = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
-                windowManager);
-        mUnfoldTransitionProgressProvider.addCallback(mAnimationListener);
+        mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
+                unfoldTransitionProgressProvider);
+
+        mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
+                windowManager));
+        mProgressProvider.addCallback(new QsbAnimationListener());
     }
 
     /**
@@ -73,7 +70,7 @@
             @Override
             public boolean onPreDraw() {
                 if (obs.isAlive()) {
-                    onPreDrawAfterResume();
+                    mProgressProvider.setReadyToHandleTransition(true);
                     obs.removeOnPreDrawListener(this);
                 }
                 return true;
@@ -85,12 +82,7 @@
      * Called when launcher activity is paused
      */
     public void onPause() {
-        if (mIsTransitionRunning) {
-            mIsTransitionRunning = false;
-            mAnimationListener.onTransitionFinished();
-        }
-
-        mIsReadyToPlayAnimation = false;
+        mProgressProvider.setReadyToHandleTransition(false);
         mQsbInsettable = null;
     }
 
@@ -98,48 +90,24 @@
      * Called when launcher activity is destroyed
      */
     public void onDestroy() {
-        mUnfoldTransitionProgressProvider.removeCallback(mAnimationListener);
+        mProgressProvider.destroy();
     }
 
-    /**
-     * Called after performing layouting of the views after configuration change
-     */
-    private void onPreDrawAfterResume() {
-        mIsReadyToPlayAnimation = true;
-
-        if (mIsTransitionRunning) {
-            mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
-        }
-    }
-
-    private class AnimationListener implements TransitionProgressListener {
+    private class QsbAnimationListener implements TransitionProgressListener {
 
         @Override
         public void onTransitionStarted() {
-            mIsTransitionRunning = true;
-
-            if (mIsReadyToPlayAnimation) {
-                mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
-            }
         }
 
         @Override
         public void onTransitionFinished() {
-            if (mIsReadyToPlayAnimation) {
-                mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
-
-                if (mQsbInsettable != null) {
-                    mQsbInsettable.setHorizontalInsets(0);
-                }
+            if (mQsbInsettable != null) {
+                mQsbInsettable.setHorizontalInsets(0);
             }
-
-            mIsTransitionRunning = false;
         }
 
         @Override
         public void onTransitionProgress(float progress) {
-            mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress);
-
             if (mQsbInsettable != null) {
                 float insetPercentage = comp(progress) * MAX_WIDTH_INSET_FRACTION;
                 mQsbInsettable.setHorizontalInsets(insetPercentage);
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
new file mode 100644
index 0000000..effdfdd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
+
+/**
+ * Class that allows to set translations for move from center animation independently
+ * from other translations for certain launcher views
+ */
+public class LauncherViewsMoveFromCenterTranslationApplier implements TranslationApplier {
+
+    @Override
+    public void apply(@NonNull View view, float x, float y) {
+        if (view instanceof NavigableAppWidgetHostView) {
+            ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
+        } else if (view instanceof BubbleTextView) {
+            ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
+        } else if (view instanceof FolderIcon) {
+            ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+        } else {
+            view.setTranslationX(x);
+            view.setTranslationY(y);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 0000000..2ef311f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+public final class ScopedUnfoldTransitionProgressProvider implements
+        UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+    private static final float PROGRESS_UNSET = -1f;
+
+    @Nullable
+    private UnfoldTransitionProgressProvider mSource;
+
+    private final List<TransitionProgressListener> mListeners = new ArrayList<>();
+
+    private boolean mIsReadyToHandleTransition;
+    private boolean mIsTransitionRunning;
+    private float mLastTransitionProgress = PROGRESS_UNSET;
+
+    public ScopedUnfoldTransitionProgressProvider() {
+        this(null);
+    }
+
+    public ScopedUnfoldTransitionProgressProvider(@Nullable UnfoldTransitionProgressProvider
+                                                          source) {
+        setSourceProvider(source);
+    }
+
+    /**
+     * Sets the source for the unfold transition progress updates,
+     * it replaces current provider if it is already set
+     * @param provider transition provider that emits transition progress updates
+     */
+    public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
+        if (mSource != null) {
+            mSource.removeCallback(this);
+        }
+
+        if (provider != null) {
+            mSource = provider;
+            mSource.addCallback(this);
+        }
+    }
+
+    /**
+     * Allows to notify this provide whether the listeners can play the transition or not.
+     * Call this method with readyToHandleTransition = true when all listeners
+     * are ready to consume the transition progress events.
+     * Call it with readyToHandleTransition = false when listeners can't process the events.
+     */
+    public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
+        if (mIsTransitionRunning) {
+            if (mIsReadyToHandleTransition) {
+                mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+
+                if (mLastTransitionProgress != PROGRESS_UNSET) {
+                    mListeners.forEach(listener ->
+                            listener.onTransitionProgress(mLastTransitionProgress));
+                }
+            } else {
+                mIsTransitionRunning = false;
+                mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+            }
+        }
+
+        mIsReadyToHandleTransition = isReadyToHandleTransition;
+    }
+
+    @Override
+    public void addCallback(@NonNull TransitionProgressListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(@NonNull TransitionProgressListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void destroy() {
+        mSource.removeCallback(this);
+    }
+
+    @Override
+    public void onTransitionStarted() {
+        this.mIsTransitionRunning = true;
+        if (mIsReadyToHandleTransition) {
+            mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+        }
+    }
+
+    @Override
+    public void onTransitionProgress(float progress) {
+        if (mIsReadyToHandleTransition) {
+            mListeners.forEach(listener -> listener.onTransitionProgress(progress));
+        }
+
+        mLastTransitionProgress = progress;
+    }
+
+    @Override
+    public void onTransitionFinished() {
+        if (mIsReadyToHandleTransition) {
+            mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+        }
+
+        mIsTransitionRunning = false;
+        mLastTransitionProgress = PROGRESS_UNSET;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 482092d..95403b2 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -15,20 +15,16 @@
  */
 package com.android.quickstep.util;
 
-import android.annotation.NonNull;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
-import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 
 import java.util.HashMap;
@@ -49,7 +45,7 @@
     public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
         mLauncher = launcher;
         mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
-                new WorkspaceViewsTranslationApplier());
+                new LauncherViewsMoveFromCenterTranslationApplier());
     }
 
     @Override
@@ -122,19 +118,4 @@
             view.setClipChildren(originalClipChildren);
         }
     }
-
-    private static class WorkspaceViewsTranslationApplier implements TranslationApplier {
-
-        @Override
-        public void apply(@NonNull View view, float x, float y) {
-            if (view instanceof NavigableAppWidgetHostView) {
-                ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
-            } else if (view instanceof BubbleTextView) {
-                ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
-            } else {
-                view.setTranslationX(x);
-                view.setTranslationY(y);
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e52d1be..54920e1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -90,6 +90,8 @@
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
+    private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
     private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
 
     private float mScaleForReorderBounce = 1f;
@@ -825,7 +827,8 @@
 
     private void updateTranslation() {
         super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
-                + mTranslationForMoveFromCenterAnimation.x);
+                + mTranslationForMoveFromCenterAnimation.x
+                + mTranslationXForTaskbarAlignmentAnimation);
         super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
                 + mTranslationForMoveFromCenterAnimation.y);
     }
@@ -860,11 +863,29 @@
         return mScaleForReorderBounce;
     }
 
+    /**
+     * Sets translation values for move from center animation
+     */
     public void setTranslationForMoveFromCenterAnimation(float x, float y) {
         mTranslationForMoveFromCenterAnimation.set(x, y);
         updateTranslation();
     }
 
+    /**
+     * Sets translationX for taskbar to launcher alignment animation
+     */
+    public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
+        mTranslationXForTaskbarAlignmentAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationX value for taskbar to launcher alignment animation
+     */
+    public float getTranslationXForTaskbarAlignmentAnimation() {
+        return mTranslationXForTaskbarAlignmentAnimation;
+    }
+
     public View getView() {
         return this;
     }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 60d8cdb..439df80 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -132,6 +132,9 @@
 
     private Rect mTouchArea = new Rect();
 
+    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+    private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
@@ -765,8 +768,11 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+                + mTranslationForMoveFromCenterAnimation.x
+                + mTranslationXForTaskbarAlignmentAnimation);
+        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+                + mTranslationForMoveFromCenterAnimation.y);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -778,6 +784,29 @@
         offset.set(mTranslationForReorderBounce);
     }
 
+    /**
+     * Sets translationX value for taskbar to launcher alignment animation
+     */
+    public void setTranslationForTaskbarAlignmentAnimation(float translationX) {
+        mTranslationXForTaskbarAlignmentAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar to launcher alignment animation
+     */
+    public float getTranslationXForTaskbarAlignmentAnimation() {
+        return mTranslationXForTaskbarAlignmentAnimation;
+    }
+
+    /**
+     * Sets translation values for move from center animation
+     */
+    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+        mTranslationForMoveFromCenterAnimation.set(x, y);
+        updateTranslation();
+    }
+
     @Override
     public void setReorderPreviewOffset(float x, float y) {
         mTranslationForReorderPreview.set(x, y);