Refactoring CellLayout into three classes

- splitting the rendering of children from the CellLayout to enhance performance, gives ~4 fps boost while scrolling on pages full of icons, and no change on pages full of widgets
- this will allow us to add hardware layer support trivially, which will increase performance while scrolling ~6-10 fps
- separated logic for caching celllayouts to bitmaps into a separate class

Change-Id: Ib6abeb19126e1504997b43c2f44af2a2fb3cd39f
diff --git a/src/com/android/launcher2/CachedViewGroup.java b/src/com/android/launcher2/CachedViewGroup.java
new file mode 100644
index 0000000..b5cfd60
--- /dev/null
+++ b/src/com/android/launcher2/CachedViewGroup.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 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.launcher2;
+
+import com.android.launcher.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.PorterDuff.Mode;
+import android.view.View;
+import android.view.ViewGroup;
+
+// This class caches the drawing of this View's children in a bitmap when the scale factor
+// falls below a certain size. Only used by CellLayout, but in a separate class to keep cache
+// logic separate from the other logic in CellLayout
+public class CachedViewGroup extends ViewGroup implements VisibilityChangedListener {
+    static final String TAG = "CachedViewGroup";
+
+    private Bitmap mCache;
+    private Canvas mCacheCanvas;
+    private Rect mCacheRect;
+    private Paint mCachePaint;
+
+    private boolean mIsCacheEnabled = true;
+    private boolean mDisableCacheUpdates = false;
+    private boolean mForceCacheUpdate = false;
+    private boolean isUpdatingCache = false;
+    private boolean mIsCacheDirty = true;
+    private float mBitmapCacheScale;
+    private float mMaxScaleForUsingBitmapCache;
+
+    private Rect mBackgroundRect;
+
+    public CachedViewGroup(Context context) {
+        super(context);
+        mBackgroundRect = new Rect();
+        mCacheRect = new Rect();
+        final Resources res = getResources();
+        mBitmapCacheScale =
+            res.getInteger(R.integer.config_workspaceScreenBitmapCacheScale) / 100.0f;
+        mMaxScaleForUsingBitmapCache =
+            res.getInteger(R.integer.config_maxScaleForUsingWorkspaceScreenBitmapCache) / 100.0f;
+        mCacheCanvas = new Canvas();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        // sub-classes (namely CellLayout) will need to implement this
+        prepareCacheBitmap();
+        invalidateCache();
+    }
+
+    private void invalidateIfNeeded() {
+        if (mIsCacheDirty) {
+            // Force a redraw to update the cache if it's dirty
+            invalidate();
+        }
+    }
+
+    public void enableCache() {
+        mIsCacheEnabled = true;
+        invalidateIfNeeded();
+    }
+
+    public void disableCache() {
+        mIsCacheEnabled = false;
+    }
+
+    public void disableCacheUpdates() {
+        mDisableCacheUpdates = true;
+        // Force just one update before we enter a period of no cache updates
+        mForceCacheUpdate = true;
+    }
+
+    public void enableCacheUpdates() {
+        mDisableCacheUpdates = false;
+        invalidateIfNeeded();
+    }
+
+    private void invalidateCache() {
+        mIsCacheDirty = true;
+        invalidate();
+    }
+
+    public void receiveVisibilityChangedMessage(View v) {
+        invalidateCache();
+    }
+
+    private void prepareCacheBitmap() {
+        if (mCache == null) {
+            mCache = Bitmap.createBitmap((int) (getWidth() * mBitmapCacheScale),
+                    (int) (getHeight() * mBitmapCacheScale), Config.ARGB_8888);
+
+            mCachePaint = new Paint();
+            mCachePaint.setFilterBitmap(true);
+            mCacheCanvas.setBitmap(mCache);
+            mCacheCanvas.scale(mBitmapCacheScale, mBitmapCacheScale);
+        }
+    }
+
+
+    public void updateCache() {
+        mCacheCanvas.drawColor(0, Mode.CLEAR);
+
+        float alpha = getAlpha();
+        setAlpha(1.0f);
+        isUpdatingCache = true;
+        draw(mCacheCanvas);
+        isUpdatingCache = false;
+        setAlpha(alpha);
+
+        mIsCacheDirty = false;
+    }
+
+
+    public void drawChildren(Canvas canvas) {
+        super.dispatchDraw(canvas);
+    }
+
+    @Override
+    public void removeAllViews() {
+        super.removeAllViews();
+        invalidateCache();
+    }
+
+    @Override
+    public void removeAllViewsInLayout() {
+        super.removeAllViewsInLayout();
+        invalidateCache();
+    }
+
+    public void removeViewWithoutMarkingCells(View view) {
+        super.removeView(view);
+        invalidateCache();
+    }
+
+    @Override
+    public void removeView(View view) {
+        super.removeView(view);
+        invalidateCache();
+    }
+
+    @Override
+    public void removeViewAt(int index) {
+        super.removeViewAt(index);
+        invalidateCache();
+    }
+
+    @Override
+    public void removeViewInLayout(View view) {
+        super.removeViewInLayout(view);
+        invalidateCache();
+    }
+
+    @Override
+    public void removeViews(int start, int count) {
+        super.removeViews(start, count);
+        invalidateCache();
+    }
+
+    @Override
+    public void removeViewsInLayout(int start, int count) {
+        super.removeViewsInLayout(start, count);
+        invalidateCache();
+    }
+
+    @Override
+    public void dispatchDraw(Canvas canvas) {
+        final int count = getChildCount();
+
+        boolean useBitmapCache = false;
+        if (!isUpdatingCache) {
+            if (!mIsCacheDirty) {
+                // Check if one of the children (an icon or widget) is dirty
+                for (int i = 0; i < count; i++) {
+                    final View child = getChildAt(i);
+                    if (child.isDirty()) {
+                        mIsCacheDirty = true;
+                        break;
+                    }
+                }
+            }
+
+            useBitmapCache = mIsCacheEnabled && getScaleX() < mMaxScaleForUsingBitmapCache;
+            if (mForceCacheUpdate ||
+                    (useBitmapCache && !mDisableCacheUpdates)) {
+                // Sometimes we force a cache update-- this is used to make sure the cache will look as
+                // up-to-date as possible right when we disable cache updates
+                if (mIsCacheDirty) {
+                    updateCache();
+                }
+                mForceCacheUpdate = false;
+            }
+        }
+
+        if (useBitmapCache) {
+            mCachePaint.setAlpha((int)(255*getAlpha()));
+            canvas.drawBitmap(mCache, mCacheRect, mBackgroundRect, mCachePaint);
+        } else {
+            super.dispatchDraw(canvas);
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+
+        // invalidate the cache to have it reflect the new item
+        invalidateCache();
+
+        if (child instanceof VisibilityChangedBroadcaster) {
+            VisibilityChangedBroadcaster v = (VisibilityChangedBroadcaster) child;
+            v.setVisibilityChangedListener(this);
+        }
+    }
+
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mBackgroundRect.set(0, 0, w, h);
+        mCacheRect.set(0, 0, (int) (mBitmapCacheScale * w), (int) (mBitmapCacheScale * h));
+        mCache = null;
+        prepareCacheBitmap();
+        invalidateCache();
+    }
+}
+
+
+//Custom interfaces used to listen to "visibility changed" events of *children* of Views. Avoided
+//using "onVisibilityChanged" in the names because there's a method of that name in framework
+//(which can only can be used to listen to ancestors' "visibility changed" events)
+interface VisibilityChangedBroadcaster {
+    public void setVisibilityChangedListener(VisibilityChangedListener listener);
+}
+
+interface VisibilityChangedListener {
+    public void receiveVisibilityChangedMessage(View v);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 49ae652..9c629ee 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -25,7 +25,6 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -37,8 +36,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
-import android.graphics.Bitmap.Config;
-import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -53,7 +50,7 @@
 
 import java.util.Arrays;
 
-public class CellLayout extends ViewGroup implements VisibilityChangedListener {
+public class CellLayout extends ViewGroup {
     static final String TAG = "CellLayout";
 
     private int mCellWidth;
@@ -99,18 +96,6 @@
     private float mGlowBackgroundScale;
     private float mGlowBackgroundAlpha;
 
-    private Bitmap mCache;
-    private Canvas mCacheCanvas;
-    private Rect mCacheRect;
-    private Paint mCachePaint;
-
-    private boolean mIsCacheEnabled = true;
-    private boolean mDisableCacheUpdates = false;
-    private boolean mForceCacheUpdate = false;
-    private boolean mIsCacheDirty = true;
-    private float mBitmapCacheScale;
-    private float mMaxScaleForUsingBitmapCache;
-
     private boolean mAcceptsDrops = false;
     // If we're actively dragging something over this screen, mIsDragOverlapping is true
     private boolean mIsDragOverlapping = false;
@@ -136,11 +121,10 @@
     // When a drag operation is in progress, holds the nearest cell to the touch point
     private final int[] mDragCell = new int[2];
 
-    private final WallpaperManager mWallpaperManager;
-
     private boolean mDragging = false;
 
     private TimeInterpolator mEaseOutInterpolator;
+    private CellLayoutChildren mChildren;
 
     public CellLayout(Context context) {
         this(context, null);
@@ -181,8 +165,6 @@
 
         setAlwaysDrawnWithCacheEnabled(false);
 
-        mWallpaperManager = WallpaperManager.getInstance(context);
-
         final Resources res = getResources();
 
         if (LauncherApplication.isScreenXLarge()) {
@@ -279,15 +261,13 @@
 
         mBackgroundRect = new Rect();
         mGlowBackgroundRect = new Rect();
-        mCacheRect = new Rect();
         setHoverScale(1.0f);
         setHoverAlpha(1.0f);
 
-        mBitmapCacheScale =
-            res.getInteger(R.integer.config_workspaceScreenBitmapCacheScale) / 100.0f;
-        mMaxScaleForUsingBitmapCache =
-            res.getInteger(R.integer.config_maxScaleForUsingWorkspaceScreenBitmapCache) / 100.0f;
-        mCacheCanvas = new Canvas();
+        mChildren = new CellLayoutChildren(context);
+        mChildren.setCellDimensions(
+                mCellWidth, mCellHeight, mLeftPadding, mTopPadding, mWidthGap, mHeightGap);
+        addView(mChildren);
     }
 
     public void setIsDefaultDropTarget(boolean isDefaultDropTarget) {
@@ -376,100 +356,12 @@
         }
     }
 
-    public void drawChildren(Canvas canvas) {
-        super.dispatchDraw(canvas);
-    }
-
-    private void invalidateIfNeeded() {
-        if (mIsCacheDirty) {
-            // Force a redraw to update the cache if it's dirty
-            invalidate();
-        }
-    }
-
-    public void enableCache() {
-        mIsCacheEnabled = true;
-        invalidateIfNeeded();
-    }
-
-    public void disableCache() {
-        mIsCacheEnabled = false;
-    }
-
     public void disableCacheUpdates() {
-        mDisableCacheUpdates = true;
-        // Force just one update before we enter a period of no cache updates
-        mForceCacheUpdate = true;
+        mChildren.disableCacheUpdates();
     }
 
     public void enableCacheUpdates() {
-        mDisableCacheUpdates = false;
-        invalidateIfNeeded();
-    }
-
-    private void invalidateCache() {
-        mIsCacheDirty = true;
-        invalidate();
-    }
-
-    public void receiveVisibilityChangedMessage(View v) {
-        invalidateCache();
-    }
-
-    public void updateCache() {
-        mCacheCanvas.drawColor(0, Mode.CLEAR);
-
-        float alpha = getAlpha();
-        setAlpha(1.0f);
-        drawChildren(mCacheCanvas);
-        setAlpha(alpha);
-
-        mIsCacheDirty = false;
-    }
-
-    public void dispatchDraw(Canvas canvas) {
-        final int count = getChildCount();
-
-        if (!mIsCacheDirty) {
-            // Check if one of the children (an icon or widget) is dirty
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                if (child.isDirty()) {
-                    mIsCacheDirty = true;
-                    break;
-                }
-            }
-        }
-
-        boolean useBitmapCache = mIsCacheEnabled && getScaleX() < mMaxScaleForUsingBitmapCache;
-        if (mForceCacheUpdate ||
-                (useBitmapCache && !mDisableCacheUpdates)) {
-            // Sometimes we force a cache update-- this is used to make sure the cache will look as
-            // up-to-date as possible right when we disable cache updates
-            if (mIsCacheDirty) {
-                updateCache();
-            }
-            mForceCacheUpdate = false;
-        }
-
-        if (useBitmapCache) {
-            mCachePaint.setAlpha((int)(255*getAlpha()));
-            canvas.drawBitmap(mCache, mCacheRect, mBackgroundRect, mCachePaint);
-        } else {
-            super.dispatchDraw(canvas);
-        }
-    }
-
-    private void prepareCacheBitmap() {
-        if (mCache == null) {
-            mCache = Bitmap.createBitmap((int) (getWidth() * mBitmapCacheScale),
-                    (int) (getHeight() * mBitmapCacheScale), Config.ARGB_8888);
-
-            mCachePaint = new Paint();
-            mCachePaint.setFilterBitmap(true);
-            mCacheCanvas.setBitmap(mCache);
-            mCacheCanvas.scale(mBitmapCacheScale, mBitmapCacheScale);
-        }
+        mChildren.enableCacheUpdates();
     }
 
     @Override
@@ -610,15 +502,8 @@
 
             child.setId(childId);
 
-            addView(child, index, lp);
-            child.setAlpha(getAlpha());
-            if (child instanceof VisibilityChangedBroadcaster) {
-                VisibilityChangedBroadcaster v = (VisibilityChangedBroadcaster) child;
-                v.setVisibilityChangedListener(this);
-            }
+            mChildren.addView(child, index, lp);
 
-            // invalidate the cache to have it reflect the new item
-            invalidateCache();
             if (markCells) markCellsAsOccupiedForView(child);
 
             return true;
@@ -639,70 +524,56 @@
 
     @Override
     public void removeAllViews() {
-        super.removeAllViews();
         clearOccupiedCells();
-        invalidateCache();
+        mChildren.removeAllViews();
     }
 
     @Override
     public void removeAllViewsInLayout() {
-        super.removeAllViewsInLayout();
         clearOccupiedCells();
-        invalidateCache();
+        mChildren.removeAllViewsInLayout();
     }
 
     public void removeViewWithoutMarkingCells(View view) {
-        super.removeView(view);
-        invalidateCache();
+        mChildren.removeViewWithoutMarkingCells(view);
     }
 
     @Override
     public void removeView(View view) {
         markCellsAsUnoccupiedForView(view);
-        super.removeView(view);
-        invalidateCache();
+        mChildren.removeView(view);
     }
 
     @Override
     public void removeViewAt(int index) {
-        markCellsAsUnoccupiedForView(getChildAt(index));
-        super.removeViewAt(index);
-        invalidateCache();
+        markCellsAsUnoccupiedForView(mChildren.getChildAt(index));
+        mChildren.removeViewAt(index);
     }
 
     @Override
     public void removeViewInLayout(View view) {
         markCellsAsUnoccupiedForView(view);
-        super.removeViewInLayout(view);
-        invalidateCache();
+        mChildren.removeViewInLayout(view);
     }
 
     @Override
     public void removeViews(int start, int count) {
         for (int i = start; i < start + count; i++) {
-            markCellsAsUnoccupiedForView(getChildAt(i));
+            markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
         }
-        super.removeViews(start, count);
-        invalidateCache();
+        mChildren.removeViews(start, count);
     }
 
     @Override
     public void removeViewsInLayout(int start, int count) {
         for (int i = start; i < start + count; i++) {
-            markCellsAsUnoccupiedForView(getChildAt(i));
+            markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
         }
-        super.removeViewsInLayout(start, count);
-        invalidateCache();
+        mChildren.removeViewsInLayout(start, count);
     }
 
-    @Override
-    public void requestChildFocus(View child, View focused) {
-        super.requestChildFocus(child, focused);
-        if (child != null) {
-            Rect r = new Rect();
-            child.getDrawingRect(r);
-            requestRectangleOnScreen(r);
-        }
+    public void drawChildren(Canvas canvas) {
+        mChildren.draw(canvas);
     }
 
     @Override
@@ -716,11 +587,11 @@
         final Rect frame = mRect;
         final int x = touchX + mScrollX;
         final int y = touchY + mScrollY;
-        final int count = getChildCount();
+        final int count = mChildren.getChildCount();
 
         boolean found = false;
         for (int i = count - 1; i >= 0; i--) {
-            final View child = getChildAt(i);
+            final View child = mChildren.getChildAt(i);
 
             if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
                 child.getHitRect(frame);
@@ -753,7 +624,6 @@
         setTag(cellInfo);
     }
 
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
@@ -886,68 +756,35 @@
             mWidthGap = mHeightGap = minGap;
         }
 
-        int count = getChildCount();
-
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
-                    mLeftPadding, mTopPadding);
-
-            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
-            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                    MeasureSpec.EXACTLY);
-
-            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
-        }
+        // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
+        int newWidth = widthSpecSize;
+        int newHeight = heightSpecSize;
         if (widthSpecMode == MeasureSpec.AT_MOST) {
-            int newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) +
+            newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) +
                 ((mCountX - 1) * mWidthGap);
-            int newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) +
+            newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) +
                 ((mCountY - 1) * mHeightGap);
             setMeasuredDimension(newWidth, newHeight);
-        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
-            setMeasuredDimension(widthSpecSize, heightSpecSize);
         }
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
+                    MeasureSpec.EXACTLY);
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+        }
+        setMeasuredDimension(newWidth, newHeight);
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int count = getChildCount();
-
         for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
-                int childLeft = lp.x;
-                int childTop = lp.y;
-                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
-
-                if (lp.dropped) {
-                    lp.dropped = false;
-
-                    final int[] cellXY = mTmpCellXY;
-                    getLocationOnScreen(cellXY);
-                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
-                            WallpaperManager.COMMAND_DROP,
-                            cellXY[0] + childLeft + lp.width / 2,
-                            cellXY[1] + childTop + lp.height / 2, 0, null);
-
-                    if (lp.animateDrop) {
-                        lp.animateDrop = false;
-
-                        // This call does not result in a requestLayout(), but at one point did.
-                        // We need to be cautious about any method calls within the layout pass
-                        // to insure we don't leave the view tree in a bad state.
-                        ((Workspace) mParent).animateViewIntoPosition(child);
-                    }
-                }
-            }
+            View child = getChildAt(i);
+            child.layout(0, 0, r - l, b - t);
         }
-        prepareCacheBitmap();
-        invalidateCache();
     }
 
     @Override
@@ -955,28 +792,16 @@
         super.onSizeChanged(w, h, oldw, oldh);
         mBackgroundRect.set(0, 0, w, h);
         updateGlowRect();
-        mCacheRect.set(0, 0, (int) (mBitmapCacheScale * w), (int) (mBitmapCacheScale * h));
-        mCache = null;
-        prepareCacheBitmap();
-        invalidateCache();
     }
 
     @Override
     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View view = getChildAt(i);
-            view.setDrawingCacheEnabled(enabled);
-            // Update the drawing caches
-            if (!view.isHardwareAccelerated()) {
-                view.buildDrawingCache(true);
-            }
-        }
+        mChildren.setChildrenDrawingCacheEnabled(enabled);
     }
 
     @Override
     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
-        super.setChildrenDrawnWithCacheEnabled(enabled);
+        mChildren.setChildrenDrawnWithCacheEnabled(enabled);
     }
 
     public float getBackgroundAlpha() {
@@ -1017,17 +842,7 @@
     }
 
     public View getChildAt(int x, int y) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
-                    (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
-                return child;
-            }
-        }
-        return null;
+        return mChildren.getChildAt(x, y);
     }
 
     /**
@@ -1506,13 +1321,13 @@
     }
 
     private void markCellsAsOccupiedForView(View view) {
-        if (view == null || view.getParent() != this) return;
+        if (view == null || view.getParent() != mChildren) return;
         LayoutParams lp = (LayoutParams) view.getLayoutParams();
         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
     }
 
     private void markCellsAsUnoccupiedForView(View view) {
-        if (view == null || view.getParent() != this) return;
+        if (view == null || view.getParent() != mChildren) return;
         LayoutParams lp = (LayoutParams) view.getLayoutParams();
         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
     }
@@ -1676,14 +1491,3 @@
         }
     }
 }
-
-// Custom interfaces used to listen to "visibility changed" events of *children* of Views. Avoided
-// using "onVisibilityChanged" in the names because there's a method of that name in framework
-// (which can only can be used to listen to ancestors' "visibility changed" events)
-interface VisibilityChangedBroadcaster {
-    public void setVisibilityChangedListener(VisibilityChangedListener listener);
-}
-
-interface VisibilityChangedListener {
-    public void receiveVisibilityChangedMessage(View v);
-}
diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java
new file mode 100644
index 0000000..09ab266
--- /dev/null
+++ b/src/com/android/launcher2/CellLayoutChildren.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2008 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.launcher2;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+
+public class CellLayoutChildren extends CachedViewGroup {
+    static final String TAG = "CellLayoutChildren";
+
+    // These are temporary variables to prevent having to allocate a new object just to
+    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+    private final int[] mTmpCellXY = new int[2];
+
+    private final WallpaperManager mWallpaperManager;
+
+    private int mCellWidth;
+    private int mCellHeight;
+
+    private int mLeftPadding;
+    private int mTopPadding;
+
+    private int mWidthGap;
+    private int mHeightGap;
+
+    public CellLayoutChildren(Context context) {
+        super(context);
+        mWallpaperManager = WallpaperManager.getInstance(context);
+    }
+
+    public void setCellDimensions(int cellWidth, int cellHeight,
+            int leftPadding, int topPadding, int widthGap, int heightGap ) {
+        mCellWidth = cellWidth;
+        mCellHeight = cellHeight;
+        mLeftPadding = leftPadding;
+        mTopPadding = topPadding;
+        mWidthGap = widthGap;
+        mHeightGap = heightGap;
+    }
+
+    public View getChildAt(int x, int y) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
+                    (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int cellWidth = mCellWidth;
+        final int cellHeight = mCellHeight;
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
+                    mLeftPadding, mTopPadding);
+
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+                    MeasureSpec.EXACTLY);
+
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+        }
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+                int childLeft = lp.x;
+                int childTop = lp.y;
+                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+                if (lp.dropped) {
+                    lp.dropped = false;
+
+                    final int[] cellXY = mTmpCellXY;
+                    getLocationOnScreen(cellXY);
+                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+                            WallpaperManager.COMMAND_DROP,
+                            cellXY[0] + childLeft + lp.width / 2,
+                            cellXY[1] + childTop + lp.height / 2, 0, null);
+
+                    if (lp.animateDrop) {
+                        lp.animateDrop = false;
+
+                        // This call does not result in a requestLayout(), but at one point did.
+                        // We need to be cautious about any method calls within the layout pass
+                        // to insure we don't leave the view tree in a bad state.
+                        ((Workspace) mParent.getParent()).animateViewIntoPosition(child);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (child != null) {
+            Rect r = new Rect();
+            child.getDrawingRect(r);
+            requestRectangleOnScreen(r);
+        }
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
+    @Override
+    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View view = getChildAt(i);
+            view.setDrawingCacheEnabled(enabled);
+            // Update the drawing caches
+            if (!view.isHardwareAccelerated()) {
+                view.buildDrawingCache(true);
+            }
+        }
+    }
+
+    @Override
+    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+        super.setChildrenDrawnWithCacheEnabled(enabled);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index f381b1f..dabc42a 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1963,7 +1963,7 @@
 
     void closeFolder(Folder folder) {
         folder.getInfo().opened = false;
-        ViewGroup parent = (ViewGroup) folder.getParent();
+        ViewGroup parent = (ViewGroup) folder.getParent().getParent();
         if (parent != null) {
             CellLayout cl = (CellLayout) parent;
             cl.removeViewWithoutMarkingCells(folder);
@@ -2212,7 +2212,7 @@
         }
 
         if (!(v instanceof CellLayout)) {
-            v = (View) v.getParent();
+            v = (View) v.getParent().getParent();
         }
 
 
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 4a0f44e..d99921f 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -109,7 +109,7 @@
     protected boolean mAllowOverScroll = true;
     protected int mUnboundedScrollX;
 
-    // parameter that adjusts the layout to be optimized for CellLayouts with that scale factor
+    // parameter that adjusts the layout to be optimized for pages with that scale factor
     protected float mLayoutScale = 1.0f;
 
     protected static final int INVALID_POINTER = -1;
@@ -416,20 +416,20 @@
         setMeasuredDimension(widthSize, heightSize);
     }
 
-    protected void moveToNewPageWithoutMovingCellLayouts(int newCurrentPage) {
+    protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) {
         int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
         int delta = newX - mScrollX;
 
-        final int screenCount = getChildCount();
-        for (int i = 0; i < screenCount; i++) {
-            CellLayout cl = (CellLayout) getChildAt(i);
-            cl.setX(cl.getX() + delta);
+        final int pageCount = getChildCount();
+        for (int i = 0; i < pageCount; i++) {
+            View page = (View) getChildAt(i);
+            page.setX(page.getX() + delta);
         }
         setCurrentPage(newCurrentPage);
     }
 
-    // A layout scale of 1.0f assumes that the CellLayouts, in their unshrunken state, have a
-    // scale of 1.0f. A layout scale of 0.8f assumes the CellLayouts have a scale of 0.8f, and
+    // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a
+    // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and
     // tightens the layout accordingly
     public void setLayoutScale(float childrenScale) {
         mLayoutScale = childrenScale;
@@ -451,7 +451,7 @@
         }
         // Also, the page offset has changed  (since the pages are now smaller);
         // update the page offset, but again preserving absolute X and Y coordinates
-        moveToNewPageWithoutMovingCellLayouts(mCurrentPage);
+        scrollToNewPageWithoutMovingPages(mCurrentPage);
     }
 
     @Override
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index b9a2b62..e7fb0fd 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1519,7 +1519,7 @@
             if (springLoaded) {
                 setLayoutScale(SPRING_LOADED_DRAG_SHRINK_FACTOR);
             }
-            moveToNewPageWithoutMovingCellLayouts(newCurrentPage);
+            scrollToNewPageWithoutMovingPages(newCurrentPage);
             unshrink(true, springLoaded);
         }
     }
@@ -1811,7 +1811,7 @@
      * calls, as it is called from onLayout().
      */
     public void animateViewIntoPosition(final View view) {
-        final CellLayout parent = (CellLayout) view.getParent();
+        final CellLayout parent = (CellLayout) view.getParent().getParent();
         final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
 
         // Convert the animation params to be relative to the Workspace, not the CellLayout
@@ -1918,7 +1918,7 @@
         // subsequent taps add items to that screen
         int dragTargetIndex = indexOfChild(mDragTargetLayout);
         if (mCurrentPage != dragTargetIndex && (mIsSmall || mIsInUnshrinkAnimation)) {
-            moveToNewPageWithoutMovingCellLayouts(dragTargetIndex);
+            scrollToNewPageWithoutMovingPages(dragTargetIndex);
         }
 
         if (source != this) {
@@ -1979,7 +1979,7 @@
                 }
             }
 
-            final CellLayout parent = (CellLayout) cell.getParent();
+            final CellLayout parent = (CellLayout) cell.getParent().getParent();
 
             // Prepare it to be animated into its new position
             // This must be called after the view has been re-parented