Launcher performance optimizations: shaving about 3ms off Workspace.dispatchDraw()

    -> Caching values of getChildOffset() and getChildRelativeOffset()
    -> Using fast invalidate pattern in workspace#screenscrolled
    -> eliminated updateAdjacentPageAlpha, combined with screenScrolled
       code where appropriate to take advantage of fast invalidate
    -> removed a bit of dead code (just for cleanups sake)

Change-Id: Id91542ff846d9495a0370707d02d1f647dab22d4
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 4d8a9cf..4e329d1 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -1152,8 +1152,6 @@
         layout.createHardwareLayer();
 
         invalidate();
-        forceUpdateAdjacentPagesAlpha();
-
         /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
         if (mFadeInAdjacentScreens) {
             prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages);
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 9b3a339..d8a8bb2 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -94,6 +94,9 @@
     protected float mLastMotionY;
     protected float mTotalMotionX;
     private int mLastScreenCenter = -1;
+    private int[] mChildOffsets;
+    private int[] mChildRelativeOffsets;
+    private int[] mChildOffsetsWithLayoutScale;
 
     protected final static int TOUCH_STATE_REST = 0;
     protected final static int TOUCH_STATE_SCROLLING = 1;
@@ -135,7 +138,6 @@
     private PageSwitchListener mPageSwitchListener;
 
     private ArrayList<Boolean> mDirtyPageContent;
-    private boolean mDirtyPageAlpha = true;
 
     // choice modes
     protected static final int CHOICE_MODE_NONE = 0;
@@ -386,13 +388,11 @@
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
             if (mScrollX != mScroller.getCurrX() || mScrollY != mScroller.getCurrY()) {
-                mDirtyPageAlpha = true;
                 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
             }
             invalidate();
             return true;
         } else if (mNextPage != INVALID_PAGE) {
-            mDirtyPageAlpha = true;
             mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
             mNextPage = INVALID_PAGE;
             notifyPageSwitchListener();
@@ -519,6 +519,7 @@
     // tightens the layout accordingly
     public void setLayoutScale(float childrenScale) {
         mLayoutScale = childrenScale;
+        invalidateCachedOffsets();
 
         // Now we need to do a re-layout, but preserving absolute X and Y coordinates
         int childCount = getChildCount();
@@ -596,80 +597,25 @@
         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
             mFirstLayout = false;
         }
-    }
-
-    protected void forceUpdateAdjacentPagesAlpha() {
-        mDirtyPageAlpha = true;
-        updateAdjacentPagesAlpha();
-    }
-
-    protected void updateAdjacentPagesAlpha() {
-        if (mFadeInAdjacentScreens) {
-            if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
-                int screenWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
-                int halfScreenSize = screenWidth / 2;
-                int screenCenter = mScrollX + halfScreenSize + mPaddingLeft;
-                final int childCount = getChildCount();
-                for (int i = 0; i < childCount; ++i) {
-                    View layout = (View) getPageAt(i);
-                    int childWidth = getScaledMeasuredWidth(layout);
-                    int halfChildWidth = (childWidth / 2);
-                    int childCenter = getChildOffset(i) + halfChildWidth;
-
-                    // On the first layout, we may not have a width nor a proper offset, so for now
-                    // we should just assume full page width (and calculate the offset according to
-                    // that).
-                    if (childWidth <= 0) {
-                        childWidth = screenWidth;
-                        childCenter = (i * childWidth) + (childWidth / 2);
-                    }
-
-                    int d = halfChildWidth;
-                    int distanceFromScreenCenter = childCenter - screenCenter;
-                    if (distanceFromScreenCenter > 0) {
-                        if (i > 0) {
-                            d += getScaledMeasuredWidth(getPageAt(i - 1)) / 2;
-                        } else {
-                            continue;
-                        }
-                    } else {
-                        if (i < childCount - 1) {
-                            d += getScaledMeasuredWidth(getPageAt(i + 1)) / 2;
-                        } else {
-                            continue;
-                        }
-                    }
-                    d += mPageSpacing;
-
-                    // Preventing potential divide-by-zero
-                    d = Math.max(1, d);
-
-                    float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d;
-                    dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
-                    float alpha = 1.0f - dimAlpha;
-
-                    if (alpha < ALPHA_QUANTIZE_LEVEL) {
-                        alpha = 0.0f;
-                    } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
-                        alpha = 1.0f;
-                    }
-
-                    // Due to the way we're setting alpha on our children in PagedViewCellLayout,
-                    // this optimization causes alpha to not be properly updated sometimes (repro
-                    // case: in xlarge mode, swipe to second page in All Apps, then click on "My
-                    // Apps" tab. the page will have alpha 0 until you swipe it). Removing
-                    // optimization fixes the issue, but we should fix this in a better manner
-                    //if (Float.compare(alpha, layout.getAlpha()) != 0) {
-                        layout.setAlpha(alpha);
-                    //}
-                }
-                mDirtyPageAlpha = false;
-            }
-        }
+        invalidateCachedOffsets();
     }
 
     protected void screenScrolled(int screenCenter) {
-        updateScrollingIndicator();
+        if (isScrollingIndicatorEnabled()) {
+            updateScrollingIndicator();
+        }
+        if (mFadeInAdjacentScreens) {
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (child != null) {
+                    float scrollProgress = getScrollProgress(screenCenter, child, i);
+                    float alpha = 1 - Math.abs(scrollProgress);
+                    child.setFastAlpha(alpha);
+                    child.fastInvalidate();
+                }
+            }
+            invalidate();
+        }
     }
 
     @Override
@@ -682,6 +628,71 @@
         invalidate();
     }
 
+    protected void invalidateCachedOffsets() {
+        int count = getChildCount();
+        if (count == 0) return;
+
+        mChildOffsets = new int[count];
+        mChildRelativeOffsets = new int[count];
+        mChildOffsetsWithLayoutScale = new int[count];
+        for (int i = 0; i < count; i++) {
+            mChildOffsets[i] = -1;
+            mChildRelativeOffsets[i] = -1;
+            mChildOffsetsWithLayoutScale[i] = -1;
+        }
+    }
+
+    protected int getChildOffset(int index) {
+        int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
+                mChildOffsets : mChildOffsetsWithLayoutScale;
+
+        if (childOffsets != null && childOffsets[index] != -1) {
+            return childOffsets[index];
+        } else {
+            if (getChildCount() == 0)
+                return 0;
+
+            int offset = getRelativeChildOffset(0);
+            for (int i = 0; i < index; ++i) {
+                offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
+            }
+            if (childOffsets != null) {
+                childOffsets[index] = offset;
+            }
+            return offset;
+        }
+    }
+
+    protected int getRelativeChildOffset(int index) {
+        if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
+            return mChildRelativeOffsets[index];
+        } else {
+            final int padding = mPaddingLeft + mPaddingRight;
+            final int offset = mPaddingLeft +
+                    (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
+            if (mChildRelativeOffsets != null) {
+                mChildRelativeOffsets[index] = offset;
+            }
+            return offset;
+        }
+    }
+
+    protected int getScaledRelativeChildOffset(int index) {
+        final int padding = mPaddingLeft + mPaddingRight;
+        final int offset = mPaddingLeft + (getMeasuredWidth() - padding -
+                getScaledMeasuredWidth(getPageAt(index))) / 2;
+        return offset;
+    }
+
+    protected int getScaledMeasuredWidth(View child) {
+        // This functions are called enough times that it actually makes a difference in the
+        // profiler -- so just inline the max() here
+        final int measuredWidth = child.getMeasuredWidth();
+        final int minWidth = mMinimumWidth;
+        final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
+        return (int) (maxWidth * mLayoutScale + 0.5f);
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         int halfScreenSize = getMeasuredWidth() / 2;
@@ -689,7 +700,6 @@
 
         if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
             screenScrolled(screenCenter);
-            updateAdjacentPagesAlpha();
             mLastScreenCenter = screenCenter;
             mForceScreenScrolled = false;
         }
@@ -1322,13 +1332,6 @@
         return -1;
     }
 
-    protected void setMinimumWidthOverride(int minimumWidth) {
-        mMinimumWidth = minimumWidth;
-    }
-    protected void resetMinimumWidthOverride() {
-        mMinimumWidth = 0;
-    }
-
     protected int getChildWidth(int index) {
         // This functions are called enough times that it actually makes a difference in the
         // profiler -- so just inline the max() here
@@ -1337,36 +1340,6 @@
         return (minWidth > measuredWidth) ? minWidth : measuredWidth;
     }
 
-    protected int getRelativeChildOffset(int index) {
-        int padding = mPaddingLeft + mPaddingRight;
-        return mPaddingLeft + (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
-    }
-    protected int getScaledRelativeChildOffset(int index) {
-        int padding = mPaddingLeft + mPaddingRight;
-        return mPaddingLeft + (getMeasuredWidth() - padding -
-                getScaledMeasuredWidth(getPageAt(index))) / 2;
-    }
-
-    protected int getChildOffset(int index) {
-        if (getChildCount() == 0)
-            return 0;
-
-        int offset = getRelativeChildOffset(0);
-        for (int i = 0; i < index; ++i) {
-            offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
-        }
-        return offset;
-    }
-
-    protected int getScaledMeasuredWidth(View child) {
-        // This functions are called enough times that it actually makes a difference in the
-        // profiler -- so just inline the max() here
-        final int measuredWidth = child.getMeasuredWidth();
-        final int minWidth = mMinimumWidth;
-        final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
-        return (int) (maxWidth * mLayoutScale + 0.5f);
-    }
-
     int getPageNearestToCenterOfScreen() {
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
@@ -1726,8 +1699,6 @@
 
             // Load any pages that are necessary for the current window of views
             loadAssociatedPages(mCurrentPage, immediateAndOnly);
-            mDirtyPageAlpha = true;
-            updateAdjacentPagesAlpha();
             requestLayout();
         }
     }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 0c297dd..158a851 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1151,10 +1151,16 @@
                                 backgroundAlphaInterpolator(Math.abs(scrollProgress)));
                     }
                 }
-                cl.setTranslationX(translationX);
-                cl.setRotationY(rotation);
+                cl.setFastTranslationX(translationX);
+                cl.setFastRotationY(rotation);
+                if (mFadeInAdjacentScreens && !isSmall()) {
+                    float alpha = 1 - Math.abs(scrollProgress);
+                    cl.setFastAlpha(alpha);
+                }
+                cl.fastInvalidate();
             }
         }
+        invalidate();
     }
 
     private void resetCellLayoutTransforms(CellLayout cl, boolean left) {
@@ -1188,10 +1194,12 @@
 
     @Override
     protected void screenScrolled(int screenCenter) {
-        super.screenScrolled(screenCenter);
         if (LauncherApplication.isScreenLarge()) {
+            // We don't call super.screenScrolled() here because we handle the adjacent pages alpha
+            // ourselves (for efficiency), and there are no scrolling indicators to update.
             screenScrolledLargeUI(screenCenter);
         } else {
+            super.screenScrolled(screenCenter);
             screenScrolledStandardUI(screenCenter);
         }
     }
@@ -1379,13 +1387,6 @@
                 position[0], position[1], 0, null);
     }
 
-    @Override
-    protected void updateAdjacentPagesAlpha() {
-        if (!isSmall()) {
-            super.updateAdjacentPagesAlpha();
-        }
-    }
-
     /*
      * This interpolator emulates the rate at which the perceived scale of an object changes
      * as its distance from a camera increases. When this interpolator is applied to a scale
@@ -1545,7 +1546,6 @@
 
         final State oldState = mState;
         final boolean oldStateIsNormal = (oldState == State.NORMAL);
-        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
         final boolean oldStateIsSmall = (oldState == State.SMALL);
         mState = state;
         final boolean stateIsNormal = (state == State.NORMAL);