Merge "Removing static launcher activity dependency from FirstFrameAnimationHelper" into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index aa51e52..0326bbd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1269,11 +1269,10 @@
 
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
-                anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex),
-                        new PropertyListBuilder()
-                                .translationX(mIsRtl ? -displacementX : displacementX)
-                                .scale(1)
-                                .build()));
+                anim.play(new PropertyListBuilder()
+                        .translationX(mIsRtl ? -displacementX : displacementX)
+                        .scale(1)
+                        .build(getPageAt(otherAdjacentTaskIndex)));
             }
         }
         return anim;
@@ -1282,11 +1281,10 @@
     private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
         AnimatorSet anim = new AnimatorSet();
         anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
-        anim.play(ObjectAnimator.ofPropertyValuesHolder(child,
-                        new PropertyListBuilder()
-                                .translationX(toScaleAndTranslation[1])
-                                .translationY(toScaleAndTranslation[2])
-                                .build()));
+        anim.play(new PropertyListBuilder()
+                .translationX(toScaleAndTranslation[1])
+                .translationY(toScaleAndTranslation[2])
+                .build(child));
         return anim;
     }
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index b2d5a3f..0f5317b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -3,8 +3,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
@@ -39,6 +37,7 @@
 
     private final Launcher mLauncher;
     private final DragViewStateAnnouncer mStateAnnouncer;
+    private final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
 
     private final View[] mDragHandles = new View[HANDLE_COUNT];
 
@@ -101,6 +100,7 @@
         mBackgroundPadding = getResources()
                 .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
         mTouchTargetWidth = 2 * mBackgroundPadding;
+        mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
     }
 
     @Override
@@ -368,12 +368,7 @@
         mDeltaX = 0;
         mDeltaY = 0;
 
-        post(new Runnable() {
-            @Override
-            public void run() {
-                snapToWidget(true);
-            }
-        });
+        post(() -> snapToWidget(true));
     }
 
     /**
@@ -433,20 +428,19 @@
             }
             requestLayout();
         } else {
-            ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(lp, this,
+            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
                     PropertyValuesHolder.ofInt("width", lp.width, newWidth),
                     PropertyValuesHolder.ofInt("height", lp.height, newHeight),
                     PropertyValuesHolder.ofInt("x", lp.x, newX),
                     PropertyValuesHolder.ofInt("y", lp.y, newY));
-            oa.addUpdateListener(a -> requestLayout());
+            mFirstFrameAnimatorHelper.addTo(oa).addUpdateListener(a -> requestLayout());
 
             AnimatorSet set = new AnimatorSet();
             set.play(oa);
             for (int i = 0; i < HANDLE_COUNT; i++) {
-                set.play(LauncherAnimUtils.ofPropertyValuesHolder(mDragHandles[i],
-                        PropertyValuesHolder.ofFloat(ALPHA, 1f)));
+                set.play(mFirstFrameAnimatorHelper.addTo(
+                        ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
             }
-
             set.setDuration(SNAP_DURATION);
             set.start();
         }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 260abd1..f52a4d6 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -1921,7 +1923,7 @@
         public static final int MODE_PREVIEW = 1;
 
         float animationProgress = 0;
-        Animator a;
+        ValueAnimator a;
 
         public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
                 int cellY1, int spanX, int spanY) {
@@ -2039,14 +2041,14 @@
             }
 
             setInitialAnimationValues(true);
-            a = LauncherAnimUtils.ofPropertyValuesHolder(child,
-                    new PropertyListBuilder()
-                            .scale(initScale)
-                            .translationX(initDeltaX)
-                            .translationY(initDeltaY)
-                            .build())
+            a = new PropertyListBuilder()
+                    .scale(initScale)
+                    .translationX(initDeltaX)
+                    .translationY(initDeltaY)
+                    .build(child)
                     .setDuration(REORDER_ANIMATION_DURATION);
-            a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+            mLauncher.getDragController().addFirstFrameAnimationHelper(a);
+            a.setInterpolator(DEACCEL_1_5);
             a.start();
         }
     }
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 48c1059..c967a96 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -13,108 +13,124 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 
 import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewTreeObserver;
-
-import com.android.launcher3.util.Thunk;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
 
 /*
  *  This is a helper class that listens to updates from the corresponding animation.
  *  For the first two frames, it adjusts the current play time of the animation to
  *  prevent jank at the beginning of the animation
  */
-public class FirstFrameAnimatorHelper implements ValueAnimator.AnimatorUpdateListener {
+public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateChangeListener {
+
     private static final String TAG = "FirstFrameAnimatorHlpr";
     private static final boolean DEBUG = false;
     private static final int MAX_DELAY = 1000;
-    private final View mTarget;
-    private long mStartFrame;
-    private long mStartTime = -1;
-    private boolean mHandlingOnAnimationUpdate;
-    private boolean mAdjustedSecondFrameTime;
 
-    private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
-    @Thunk static long sGlobalFrameCounter;
-    private static boolean sVisible;
+    private View mRootView;
+    private long mGlobalFrameCount;
 
-    public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
-        mTarget = target;
-        animator.addUpdateListener(this);
-    }
-
-    public static void setIsVisible(boolean visible) {
-        sVisible = visible;
-    }
-
-    public static void initializeDrawListener(View view) {
-        if (sGlobalDrawListener != null) {
-            view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
+    public FirstFrameAnimatorHelper(View target) {
+        target.addOnAttachStateChangeListener(this);
+        if (target.isAttachedToWindow()) {
+            onViewAttachedToWindow(target);
         }
+    }
 
-        sGlobalDrawListener = () -> sGlobalFrameCounter++;
-        view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
-        sVisible = true;
+    public <T extends ValueAnimator> T addTo(T anim) {
+        anim.addUpdateListener(new MyListener());
+        return anim;
     }
 
     @Override
-    public void onAnimationUpdate(final ValueAnimator animation) {
-        final long currentTime = System.currentTimeMillis();
-        if (mStartTime == -1) {
-            mStartFrame = sGlobalFrameCounter;
-            mStartTime = currentTime;
-        }
+    public void onDraw() {
+        mGlobalFrameCount ++;
+    }
 
-        final long currentPlayTime = animation.getCurrentPlayTime();
-        boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+    @Override
+    public void onViewAttachedToWindow(View view) {
+        mRootView = view.getRootView();
+        mRootView.getViewTreeObserver().addOnDrawListener(this);
+    }
 
-        if (!mHandlingOnAnimationUpdate &&
-            sVisible &&
-            // If the current play time exceeds the duration, or the animated fraction is 1,
-            // the animation will get finished, even if we call setCurrentPlayTime -- therefore
-            // don't adjust the animation in that case
-            currentPlayTime < animation.getDuration() && !isFinalFrame) {
-            mHandlingOnAnimationUpdate = true;
-            long frameNum = sGlobalFrameCounter - mStartFrame;
-            // If we haven't drawn our first frame, reset the time to t = 0
-            // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
-            // are no longer in the foreground and no frames are being rendered ever)
-            if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
-                // The first frame on animations doesn't always trigger an invalidate...
-                // force an invalidate here to make sure the animation continues to advance
-                mTarget.getRootView().invalidate();
-                animation.setCurrentPlayTime(0);
-            // For the second frame, if the first frame took more than 16ms,
-            // adjust the start time and pretend it took only 16ms anyway. This
-            // prevents a large jump in the animation due to an expensive first frame
-            } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
-                       !mAdjustedSecondFrameTime &&
-                       currentTime > mStartTime + SINGLE_FRAME_MS &&
-                       currentPlayTime > SINGLE_FRAME_MS) {
-                animation.setCurrentPlayTime(SINGLE_FRAME_MS);
-                mAdjustedSecondFrameTime = true;
-            } else {
-                if (frameNum > 1) {
-                    mTarget.post(() -> animation.removeUpdateListener(this));
-                }
-                if (DEBUG) print(animation);
-            }
-            mHandlingOnAnimationUpdate = false;
-        } else {
-            if (DEBUG) print(animation);
+    @Override
+    public void onViewDetachedFromWindow(View view) {
+        if (mRootView != null) {
+            mRootView.getViewTreeObserver().removeOnDrawListener(this);
+            mRootView = null;
         }
     }
 
-    public void print(ValueAnimator animation) {
-        float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
-        Log.d(TAG, sGlobalFrameCounter +
-              "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
-              mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
+    private class MyListener implements AnimatorUpdateListener {
+
+        private long mStartFrame;
+        private long mStartTime = -1;
+        private boolean mHandlingOnAnimationUpdate;
+        private boolean mAdjustedSecondFrameTime;
+
+        @Override
+        public void onAnimationUpdate(final ValueAnimator animation) {
+            final long currentTime = System.currentTimeMillis();
+            if (mStartTime == -1) {
+                mStartFrame = mGlobalFrameCount;
+                mStartTime = currentTime;
+            }
+
+            final long currentPlayTime = animation.getCurrentPlayTime();
+            boolean isFinalFrame = Float.compare(1f, animation.getAnimatedFraction()) == 0;
+
+            if (!mHandlingOnAnimationUpdate &&
+                    mRootView != null &&
+                    mRootView.getWindowVisibility() == View.VISIBLE &&
+                    // If the current play time exceeds the duration, or the animated fraction is 1,
+                    // the animation will get finished, even if we call setCurrentPlayTime --
+                    // therefore don't adjust the animation in that case
+                    currentPlayTime < animation.getDuration() && !isFinalFrame) {
+                mHandlingOnAnimationUpdate = true;
+                long frameNum = mGlobalFrameCount - mStartFrame;
+
+                // If we haven't drawn our first frame, reset the time to t = 0
+                // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
+                // are no longer in the foreground and no frames are being rendered ever)
+                if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY && currentPlayTime > 0) {
+                    // The first frame on animations doesn't always trigger an invalidate...
+                    // force an invalidate here to make sure the animation continues to advance
+                    mRootView.invalidate();
+                    animation.setCurrentPlayTime(0);
+                    // For the second frame, if the first frame took more than 16ms,
+                    // adjust the start time and pretend it took only 16ms anyway. This
+                    // prevents a large jump in the animation due to an expensive first frame
+                } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+                        !mAdjustedSecondFrameTime &&
+                        currentTime > mStartTime + SINGLE_FRAME_MS &&
+                        currentPlayTime > SINGLE_FRAME_MS) {
+                    animation.setCurrentPlayTime(SINGLE_FRAME_MS);
+                    mAdjustedSecondFrameTime = true;
+                } else {
+                    if (frameNum > 1) {
+                        mRootView.post(() -> animation.removeUpdateListener(this));
+                    }
+                    if (DEBUG) print(animation);
+                }
+                mHandlingOnAnimationUpdate = false;
+            } else {
+                if (DEBUG) print(animation);
+            }
+        }
+
+        public void print(ValueAnimator animation) {
+            float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
+            Log.d(TAG, mGlobalFrameCount +
+                    "(" + (mGlobalFrameCount - mStartFrame) + ") " + mRootView + " dirty? " +
+                    mRootView.isDirty() + " " + flatFraction + " " + this + " " + animation);
+        }
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9650e4e..0dffed4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -31,7 +31,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
@@ -79,6 +78,7 @@
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -740,8 +740,6 @@
     @Override
     protected void onStop() {
         super.onStop();
-        FirstFrameAnimatorHelper.setIsVisible(false);
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
@@ -762,8 +760,6 @@
     @Override
     protected void onStart() {
         super.onStart();
-        FirstFrameAnimatorHelper.setIsVisible(true);
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
@@ -1131,7 +1127,6 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onAttachedToWindow();
         }
@@ -1839,7 +1834,6 @@
     @Override
     public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
         // Get the list of added items and intersect them with the set of items here
-        final AnimatorSet anim = new AnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<>();
         final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
@@ -1911,34 +1905,31 @@
             }
         }
 
-        if (animateIcons) {
-            // Animate to the correct page
-            if (newItemsScreenId > -1) {
-                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
-                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
-                final Runnable startBounceAnimRunnable = new Runnable() {
-                    public void run() {
-                        anim.playTogether(bounceAnims);
-                        anim.start();
-                    }
-                };
-                if (newItemsScreenId != currentScreenId) {
-                    // We post the animation slightly delayed to prevent slowdowns
-                    // when we are loading right after we return to launcher.
-                    mWorkspace.postDelayed(new Runnable() {
-                        public void run() {
-                            if (mWorkspace != null) {
-                                AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+        // Animate to the correct page
+        if (animateIcons && newItemsScreenId > -1) {
+            AnimatorSet anim = new AnimatorSet();
+            anim.playTogether(bounceAnims);
 
-                                mWorkspace.snapToPage(newScreenIndex);
-                                mWorkspace.postDelayed(startBounceAnimRunnable,
-                                        NEW_APPS_ANIMATION_DELAY);
-                            }
+            long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+            final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
+            final Runnable startBounceAnimRunnable = anim::start;
+
+            if (newItemsScreenId != currentScreenId) {
+                // We post the animation slightly delayed to prevent slowdowns
+                // when we are loading right after we return to launcher.
+                mWorkspace.postDelayed(new Runnable() {
+                    public void run() {
+                        if (mWorkspace != null) {
+                            AbstractFloatingView.closeAllOpenViews(Launcher.this, false);
+
+                            mWorkspace.snapToPage(newScreenIndex);
+                            mWorkspace.postDelayed(startBounceAnimRunnable,
+                                    NEW_APPS_ANIMATION_DELAY);
                         }
-                    }, NEW_APPS_PAGE_MOVE_DELAY);
-                } else {
-                    mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
-                }
+                    }
+                }, NEW_APPS_PAGE_MOVE_DELAY);
+            } else {
+                mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
             }
         }
         workspace.requestLayout();
@@ -2161,11 +2152,8 @@
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
-        ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
-                PropertyValuesHolder.ofFloat(View.ALPHA, 1f),
-                PropertyValuesHolder.ofFloat(View.SCALE_X, 1f),
-                PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f));
-        bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+        ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
+                .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
         bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
         return bounceAnim;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 7944ec5..ac07e88 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.graphics.drawable.Drawable;
 import android.util.Property;
 import android.view.View;
@@ -35,18 +33,6 @@
     // The progress of an animation to all apps must be at least this far along to snap to all apps.
     public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
 
-    public static ObjectAnimator ofPropertyValuesHolder(View target,
-            PropertyValuesHolder... values) {
-        return ofPropertyValuesHolder(target, target, values);
-    }
-
-    public static ObjectAnimator ofPropertyValuesHolder(Object target,
-            View view, PropertyValuesHolder... values) {
-        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values);
-        new FirstFrameAnimatorHelper(anim, view);
-        return anim;
-    }
-
     public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
             new Property<Drawable, Integer>(Integer.TYPE, "drawableAlpha") {
                 @Override
diff --git a/src/com/android/launcher3/anim/PropertyListBuilder.java b/src/com/android/launcher3/anim/PropertyListBuilder.java
index 33e7f66..acc3b45 100644
--- a/src/com/android/launcher3/anim/PropertyListBuilder.java
+++ b/src/com/android/launcher3/anim/PropertyListBuilder.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.anim;
 
+import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.view.View;
 
@@ -44,7 +45,8 @@
         return this;
     }
 
-    public PropertyValuesHolder[] build() {
-        return mProperties.toArray(new PropertyValuesHolder[mProperties.size()]);
+    public ObjectAnimator build(View view) {
+        return ObjectAnimator.ofPropertyValuesHolder(view,
+                mProperties.toArray(new PropertyValuesHolder[mProperties.size()]));
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 47bbbcb..8007754 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
 
+import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -227,6 +228,12 @@
         }
     }
 
+    public void addFirstFrameAnimationHelper(ValueAnimator anim) {
+        if (mDragObject != null && mDragObject.dragView != null) {
+            mDragObject.dragView.mFirstFrameAnimatorHelper.addTo(anim);
+        }
+    }
+
     /**
      * Call this from a drag source view like this:
      *
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 998de99..7f30ffa 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -50,12 +50,12 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
@@ -93,6 +93,7 @@
     private final Launcher mLauncher;
     private final DragLayer mDragLayer;
     @Thunk final DragController mDragController;
+    final FirstFrameAnimatorHelper mFirstFrameAnimatorHelper;
     private boolean mHasDrawn = false;
     @Thunk float mCrossFadeProgress = 0f;
     private boolean mAnimationCancelled = false;
@@ -133,6 +134,7 @@
         mLauncher = launcher;
         mDragLayer = launcher.getDragLayer();
         mDragController = launcher.getDragController();
+        mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
 
         final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
 
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 3cc9365..c7de5b0 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -156,9 +156,10 @@
         Rect fromBounds = sTempRect;
         firstNotification.getGlobalVisibleRect(fromBounds);
         float scale = (float) toBounds.height() / fromBounds.height();
-        Animator moveAndScaleIcon = ObjectAnimator.ofPropertyValuesHolder(firstNotification,
-                new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
-                        + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
+        Animator moveAndScaleIcon = new PropertyListBuilder().scale(scale)
+                .translationY(toBounds.top - fromBounds.top
+                        + (fromBounds.height() * scale - fromBounds.height()) / 2)
+                .build(firstNotification);
         moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {