Decouple the scaling / translation of widget views necessary in split-screen

=> We pull apart the scale and translation that are set in a fairly
   static way due to split-screen vs. the general translation and scale
   properties that might be used more dynamically, in this case
   for re-order animations
=> This allows removal of some code that breaks reorder animations
   due to the accrual of translations / scales in certain edge cases.
=> TODO in future CL: address other translation cases and make the throw
   case for calling base setTranslationX/Y for Workspace Items unconditional

issue 149438360

test: manual
Change-Id: Ic3fde172f669e215cd25db0fcd4e1c3c873d314f
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 93247ab..e8e88c4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -70,7 +71,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView, DraggableView {
+        IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -78,6 +79,8 @@
 
     private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
 
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
 
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -672,6 +675,30 @@
         return mIconSize;
     }
 
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(x);
+        super.setTranslationY(y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(scale);
+        super.setScaleY(scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
+    public View getView() {
+        return this;
+    }
+
     @Override
     public int getViewType() {
         return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 99416c4..71a787f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,6 +33,7 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -54,7 +55,6 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -66,7 +66,6 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -99,6 +98,7 @@
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     @Thunk final int[] mTmpPoint = new int[2];
     @Thunk final int[] mTempLocation = new int[2];
+    final PointF mTmpPointF = new PointF();
 
     // Used to visualize / debug the Grid of the CellLayout
     private static final boolean VISUALIZE_GRID = false;
@@ -136,7 +136,7 @@
     private final Paint mDragOutlinePaint = new Paint();
 
     @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
-    @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
+    @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
 
     private boolean mItemPlacementDirty = false;
 
@@ -1868,10 +1868,11 @@
             boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
                     != null && !solution.intersectingViews.contains(child);
 
+
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (c != null && !skip) {
-                ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
-                        lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+            if (c != null && !skip && (child instanceof Reorderable)) {
+                ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+                        mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
                 rha.animate();
             }
         }
@@ -1893,7 +1894,7 @@
     // Class which represents the reorder preview animations. These animations show that an item is
     // in a temporary state, and hint at where the item will return to.
     class ReorderPreviewAnimation {
-        final View child;
+        final Reorderable child;
         float finalDeltaX;
         float finalDeltaY;
         float initDeltaX;
@@ -1913,8 +1914,8 @@
         float animationProgress = 0;
         ValueAnimator a;
 
-        public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
-                int cellY1, int spanX, int spanY) {
+        public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+                int cellX1, int cellY1, int spanX, int spanY) {
             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
             final int x0 = mTmpPoint[0];
             final int y0 = mTmpPoint[1];
@@ -1926,63 +1927,60 @@
 
             this.child = child;
             this.mode = mode;
+            finalDeltaX = 0;
+            finalDeltaY = 0;
 
-            // TODO issue!
-            setInitialAnimationValues(false);
-            finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
-            finalDeltaX = initDeltaX;
-            finalDeltaY = initDeltaY;
+            child.getReorderOffset(mTmpPointF);
+            initDeltaX = mTmpPointF.x;
+            initDeltaY = mTmpPointF.y;
+            initScale = child.getReorderScale();
+            finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+
             int dir = mode == MODE_HINT ? -1 : 1;
             if (dX == dY && dX == 0) {
             } else {
                 if (dY == 0) {
-                    finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
                 } else if (dX == 0) {
-                    finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
                 } else {
                     double angle = Math.atan( (float) (dY) / dX);
-                    finalDeltaX += (int) (- dir * Math.signum(dX) *
-                            Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
-                    finalDeltaY += (int) (- dir * Math.signum(dY) *
-                            Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
+                    finalDeltaX = (int) (-dir * Math.signum(dX)
+                            * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+                    finalDeltaY = (int) (-dir * Math.signum(dY)
+                            * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
                 }
             }
         }
 
-        void setInitialAnimationValues(boolean restoreOriginalValues) {
-            if (restoreOriginalValues) {
-                if (child instanceof LauncherAppWidgetHostView) {
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
-                    initScale = lahv.getScaleToFit();
-                    initDeltaX = lahv.getTranslationForCentering().x;
-                    initDeltaY = lahv.getTranslationForCentering().y;
-                } else {
-                    initScale = mChildScale;
-                    initDeltaX = 0;
-                    initDeltaY = 0;
-                }
-            } else {
-                initScale = child.getScaleX();
-                initDeltaX = child.getTranslationX();
-                initDeltaY = child.getTranslationY();
-            }
+        void setInitialAnimationValuesToBaseline() {
+            initScale = mChildScale;
+            initDeltaX = 0;
+            initDeltaY = 0;
         }
 
         void animate() {
-            boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+            boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
 
             if (mShakeAnimators.containsKey(child)) {
                 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
-                oldAnimation.cancel();
                 mShakeAnimators.remove(child);
+
                 if (noMovement) {
-                    completeAnimationImmediately();
+                    // A previous animation for this item exists, and no new animation will exist.
+                    // Finish the old animation smoothly.
+                    oldAnimation.finishAnimation();
                     return;
+                } else {
+                    // A previous animation for this item exists, and a new one will exist. Stop
+                    // the old animation in its tracks, and proceed with the new one.
+                    oldAnimation.cancel();
                 }
             }
             if (noMovement) {
                 return;
             }
+
             ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
             a = va;
 
@@ -1999,7 +1997,7 @@
             va.addListener(new AnimatorListenerAdapter() {
                 public void onAnimationRepeat(Animator animation) {
                     // We make sure to end only after a full period
-                    setInitialAnimationValues(true);
+                    setInitialAnimationValuesToBaseline();
                     repeating = true;
                 }
             });
@@ -2012,11 +2010,9 @@
             float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
             float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
             float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
-            child.setTranslationX(x);
-            child.setTranslationY(y);
+            child.setReorderOffset(x, y);
             float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
-            child.setScaleX(s);
-            child.setScaleY(s);
+            child.setReorderScale(s);
         }
 
         private void cancel() {
@@ -2025,27 +2021,27 @@
             }
         }
 
-        @Thunk void completeAnimationImmediately() {
+        /**
+         * Smoothly returns the item to its baseline position / scale
+         */
+        @Thunk void finishAnimation() {
             if (a != null) {
                 a.cancel();
             }
 
-            setInitialAnimationValues(true);
-            a = new PropertyListBuilder()
-                    .scale(initScale)
-                    .translationX(initDeltaX)
-                    .translationY(initDeltaY)
-                    .build(child)
-                    .setDuration(REORDER_ANIMATION_DURATION);
-            Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
+            setInitialAnimationValuesToBaseline();
+            ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
+                    animationProgress, 0);
+            a = va;
             a.setInterpolator(DEACCEL_1_5);
+            a.setDuration(REORDER_ANIMATION_DURATION);
             a.start();
         }
     }
 
     private void completeAndClearReorderPreviewAnimations() {
         for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
-            a.completeAnimationImmediately();
+            a.finishAnimation();
         }
         mShakeAnimators.clear();
     }
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
new file mode 100644
index 0000000..5112eaf
--- /dev/null
+++ b/src/com/android/launcher3/Reorderable.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import android.graphics.PointF;
+import android.view.View;
+
+public interface Reorderable {
+
+    /**
+     * Set the offset related to reorder hint and "bounce" animations
+     */
+    void setReorderOffset(float x, float y);
+
+    void getReorderOffset(PointF offset);
+
+    /**
+     * Set the scale related to reorder hint and "bounce" animations
+     */
+    void setReorderScale(float scale);
+    float getReorderScale();
+
+    /**
+     * Get the com.android.view related to this object
+     */
+    View getView();
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e29971e..96bdc2a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -26,6 +26,7 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -49,6 +50,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
@@ -79,7 +81,7 @@
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
 public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
-        DraggableView {
+        DraggableView, Reorderable {
 
     @Thunk ActivityContext mActivity;
     @Thunk Folder mFolder;
@@ -119,6 +121,9 @@
     private float mDotScale;
     private Animator mDotScaleAnim;
 
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
+
     private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
             = new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
         @Override
@@ -226,16 +231,6 @@
         mBackground.getBounds(outBounds);
     }
 
-    @Override
-    public int getViewType() {
-        return DRAGGABLE_ICON;
-    }
-
-    @Override
-    public void getVisualDragBounds(Rect bounds) {
-        getPreviewBounds(bounds);
-    }
-
     public float getBackgroundStrokeWidth() {
         return mBackground.getStrokeWidth();
     }
@@ -716,4 +711,39 @@
     public void onFolderClose(int currentPage) {
         mPreviewItemManager.onFolderClose(currentPage);
     }
+
+
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(x);
+        super.setTranslationY(y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(scale);
+        super.setScaleY(scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        getPreviewBounds(bounds);
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d33f13..6f2e179 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,8 +19,6 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
@@ -40,7 +38,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.Executors;
@@ -51,7 +48,7 @@
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
+        implements TouchCompleteListener, View.OnLongClickListener {
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -73,15 +70,7 @@
     private boolean mIsAutoAdvanceRegistered;
     private Runnable mAutoAdvanceRunnable;
 
-    /**
-     * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
-     */
-    private float mScaleToFit = 1f;
 
-    /**
-     * The translation values to center the widget within its cellspans.
-     */
-    private final PointF mTranslationForCentering = new PointF(0, 0);
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
@@ -307,26 +296,6 @@
         scheduleNextAdvance();
     }
 
-    public void setScaleToFit(float scale) {
-        mScaleToFit = scale;
-        setScaleX(scale);
-        setScaleY(scale);
-    }
-
-    public float getScaleToFit() {
-        return mScaleToFit;
-    }
-
-    public void setTranslationForCentering(float x, float y) {
-        mTranslationForCentering.set(x, y);
-        setTranslationX(x);
-        setTranslationY(y);
-    }
-
-    public PointF getTranslationForCentering() {
-        return mTranslationForCentering;
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -357,17 +326,4 @@
         }
         return false;
     }
-
-    @Override
-    public int getViewType() {
-        return DRAGGABLE_WIDGET;
-    }
-
-    @Override
-    public void getVisualDragBounds(Rect bounds) {
-        int width = (int) (getMeasuredWidth() * mScaleToFit);
-        int height = (int) (getMeasuredHeight() * mScaleToFit);
-
-        bounds.set(0, 0 , width, height);
-    }
 }
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 104ad77..5ea01d9 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -18,12 +18,14 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 
+import com.android.launcher3.Reorderable;
 import com.android.launcher3.dragndrop.DraggableView;
 
 import java.util.ArrayList;
@@ -32,7 +34,20 @@
  * Extension of AppWidgetHostView with support for controlled keyboard navigation.
  */
 public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
-        implements DraggableView {
+        implements DraggableView, Reorderable {
+
+    /**
+     * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+     */
+    private float mScaleToFit = 1f;
+
+    /**
+     * The translation values to center the widget within its cellspans.
+     */
+    private final PointF mTranslationForCentering = new PointF(0, 0);
+
+    private final PointF mTranslationForReorder = new PointF(0, 0);
+    private float mScaleForReorder = 1f;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
@@ -137,6 +152,47 @@
         setSelected(childIsFocused);
     }
 
+    public void setScaleToFit(float scale) {
+        mScaleToFit = scale;
+        super.setScaleX(scale * mScaleForReorder);
+        super.setScaleY(scale * mScaleForReorder);
+    }
+
+    public float getScaleToFit() {
+        return mScaleToFit;
+    }
+
+    public View getView() {
+        return this;
+    }
+
+
+    public void setTranslationForCentering(float x, float y) {
+        mTranslationForCentering.set(x, y);
+        super.setTranslationX(x + mTranslationForReorder.x);
+        super.setTranslationY(y + mTranslationForReorder.y);
+    }
+
+    public void setReorderOffset(float x, float y) {
+        mTranslationForReorder.set(x, y);
+        super.setTranslationX(mTranslationForCentering.x + x);
+        super.setTranslationY(mTranslationForCentering.y + y);
+    }
+
+    public void getReorderOffset(PointF offset) {
+        offset.set(mTranslationForReorder);
+    }
+
+    public void setReorderScale(float scale) {
+        mScaleForReorder = scale;
+        super.setScaleX(mScaleToFit * scale);
+        super.setScaleY(mScaleToFit * scale);
+    }
+
+    public float getReorderScale() {
+        return mScaleForReorder;
+    }
+
     @Override
     public int getViewType() {
         return DRAGGABLE_WIDGET;
@@ -144,6 +200,9 @@
 
     @Override
     public void getVisualDragBounds(Rect bounds) {
-        bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+        int width = (int) (getMeasuredWidth() * mScaleToFit);
+        int height = (int) (getMeasuredHeight() * mScaleToFit);
+
+        bounds.set(0, 0 , width, height);
     }
 }