Refactor FolderIcon to separate the preview effect into it's own class

-> Created com.android.launcher3.folder package to house most folder-related files
   (aside from the FolderInfo) which is more related to the model than the UI.

Change-Id: I767063e1e4c775c01a799a3bede30cd94ac48ade
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ed14439..dddd826 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -25,7 +25,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Region;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -41,6 +40,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a20a080..5832b9f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -48,13 +48,14 @@
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
-import com.android.launcher3.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Thunk;
 
@@ -815,7 +816,7 @@
         return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
     }
 
-    int getCellWidth() {
+    public int getCellWidth() {
         return mCellWidth;
     }
 
@@ -2623,7 +2624,7 @@
      * @return The X, Y cell of a vacant area that can contain this object,
      *         nearest the requested location.
      */
-    int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+    public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
         return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
     }
 
@@ -2967,7 +2968,7 @@
     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
     //    the CellLayout that was long clicked
     public static final class CellInfo {
-        View cell;
+        public View cell;
         int cellX = -1;
         int cellY = -1;
         int spanX;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 173e6ab..997ded2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -24,6 +24,7 @@
 import android.view.animation.AnimationUtils;
 
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.util.FlingAnimation;
 import com.android.launcher3.util.Thunk;
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ce01bad..cd58230 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -342,7 +342,7 @@
     }
 
     /** Returns the workspace padding in the specified orientation */
-    Rect getWorkspacePadding(boolean isLayoutRtl) {
+    public Rect getWorkspacePadding(boolean isLayoutRtl) {
         Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
         Rect padding = new Rect();
         if (isVerticalBarLayout()) {
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 2af6a73..f99c08a 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -23,6 +23,8 @@
 import android.view.ViewGroup;
 
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderPagedView;
 import com.android.launcher3.util.FocusLogic;
 import com.android.launcher3.util.Thunk;
 
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 6e0dcd4..861a935 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -48,7 +48,7 @@
     /**
      * Whether this folder has been opened
      */
-    boolean opened;
+    public boolean opened;
 
     public int options;
 
@@ -105,7 +105,7 @@
 
     }
 
-    void addListener(FolderListener listener) {
+    public void addListener(FolderListener listener) {
         listeners.add(listener);
     }
 
@@ -127,7 +127,7 @@
         listeners.clear();
     }
 
-    interface FolderListener {
+    public interface FolderListener {
         public void onAdd(ShortcutInfo item);
         public void onRemove(ShortcutInfo item);
         public void onTitleChanged(CharSequence title);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 414ebb4..545768f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -107,6 +107,8 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
@@ -366,7 +368,7 @@
     }
 
     private Stats mStats;
-    FocusIndicatorView mFocusHandler;
+    public FocusIndicatorView mFocusHandler;
     private boolean mRotationEnabled = false;
 
     @Thunk void setOrientation() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d8bc631..f707ec5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,6 +57,8 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
@@ -621,7 +623,7 @@
      * Adds an item to the DB if it was not created previously, or move it to a new
      * <container, screen, cellX, cellY>
      */
-    static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
+    public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
             long screenId, int cellX, int cellY) {
         if (item.container == ItemInfo.NO_ID) {
             // From all apps
@@ -802,7 +804,7 @@
      * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
      * cellX, cellY have already been updated on the ItemInfos.
      */
-    static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
+    public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
             final long container, final int screen) {
 
         ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 7109895..c826c5f 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -343,7 +343,7 @@
     int getViewportWidth() {
         return mViewport.width();
     }
-    int getViewportHeight() {
+    public int getViewportHeight() {
         return mViewport.height();
     }
 
@@ -392,7 +392,7 @@
         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
     }
 
-    int getPageCount() {
+    public int getPageCount() {
         return getChildCount();
     }
 
@@ -2020,7 +2020,7 @@
         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
     }
 
-    protected void snapToPageImmediately(int whichPage) {
+    public void snapToPageImmediately(int whichPage) {
         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
     }
 
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index 908c8b9..b064c47 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -12,7 +12,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 
-class PreloadIconDrawable extends Drawable {
+public class PreloadIconDrawable extends Drawable {
 
     private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
     private static final float ANIMATION_PROGRESS_STARTED = 0f;
@@ -30,7 +30,7 @@
     private boolean mIndicatorRectDirty;
 
     private final Paint mPaint;
-    final Drawable mIcon;
+    public final Drawable mIcon;
 
     private Drawable mBgDrawable;
     private int mRingOutset;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index a70a395..128d695 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.folder.FolderIcon;
 
 import java.util.ArrayList;
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 00854fa..5d2f451 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,7 +53,9 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
-import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
@@ -908,7 +910,7 @@
 
     // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
     // See implementation for parameter definition.
-    void addInScreenFromBind(View child, long container, long screenId, int x, int y,
+    public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
             int spanX, int spanY) {
         addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
     }
@@ -3942,7 +3944,7 @@
         stripEmptyScreens();
     }
 
-    interface ItemOperator {
+    public interface ItemOperator {
         /**
          * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
          *
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
index ff99890..d271f1d 100644
--- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
@@ -17,7 +17,7 @@
 package com.android.launcher3.accessibility;
 
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.FolderPagedView;
+import com.android.launcher3.folder.FolderPagedView;
 import com.android.launcher3.R;
 
 /**
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 03731d1..8560b21 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -21,7 +21,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
-import com.android.launcher3.Folder;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InfoDropTarget;
 import com.android.launcher3.ItemInfo;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 6acbca5..4df7edd 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Folder;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 1ad8e95..3128db2 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -42,22 +42,21 @@
 
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.Folder;
-import com.android.launcher3.FolderIcon;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.R;
 import com.android.launcher3.SearchDropTargetBar;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.util.Thunk;
 
-import com.android.launcher3.R;
-
 import java.util.ArrayList;
 
 /**
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/folder/Folder.java
similarity index 97%
rename from src/com/android/launcher3/Folder.java
rename to src/com/android/launcher3/folder/Folder.java
index 850e326..a411c48 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.folder;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -52,9 +52,28 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.Alarm;
+import com.android.launcher3.CellLayout;
 import com.android.launcher3.CellLayout.CellInfo;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LogDecelerateInterpolator;
+import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Stats;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.config.FeatureFlags;
@@ -122,13 +141,13 @@
 
     protected final Launcher mLauncher;
     protected DragController mDragController;
-    protected FolderInfo mInfo;
+    public FolderInfo mInfo;
 
     @Thunk FolderIcon mFolderIcon;
 
     @Thunk FolderPagedView mContent;
     @Thunk View mContentWrapper;
-    ExtendedEditText mFolderName;
+    public ExtendedEditText mFolderName;
 
     private View mFooter;
     private int mFooterHeight;
@@ -1074,10 +1093,10 @@
         lp.y = top;
     }
 
-    float getPivotXForIconAnimation() {
+    public float getPivotXForIconAnimation() {
         return mFolderIconPivotX;
     }
-    float getPivotYForIconAnimation() {
+    public float getPivotYForIconAnimation() {
         return mFolderIconPivotY;
     }
 
@@ -1200,7 +1219,7 @@
         mDestroyed = true;
     }
 
-    boolean isDestroyed() {
+    public boolean isDestroyed() {
         return mDestroyed;
     }
 
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
similarity index 90%
rename from src/com/android/launcher3/FolderIcon.java
rename to src/com/android/launcher3/folder/FolderIcon.java
index 26b9693..8b95e76 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.folder;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -41,8 +41,30 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.launcher3.Alarm;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.PreloadIconDrawable;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.SimpleOnStylusPressListener;
+import com.android.launcher3.StylusEventHelper;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.util.Thunk;
@@ -50,19 +72,22 @@
 import java.util.ArrayList;
 
 /**
- * An icon that can appear on in the workspace representing an {@link UserFolder}.
+ * An icon that can appear on in the workspace representing an {@link Folder}.
  */
 public class FolderIcon extends FrameLayout implements FolderListener {
-    @Thunk Launcher mLauncher;
+    @Thunk
+    Launcher mLauncher;
     @Thunk Folder mFolder;
     private FolderInfo mInfo;
     @Thunk static boolean sStaticValuesDirty = true;
 
+    // TODO(adamcohen): remove this
+    public static final int NUM_ITEMS_IN_PREVIEW = 3;
+
     private CheckLongPressHelper mLongPressHelper;
     private StylusEventHelper mStylusEventHelper;
 
     // The number of icons to display in the
-    public static final int NUM_ITEMS_IN_PREVIEW = 3;
     private static final int CONSUMPTION_ANIMATION_DURATION = 100;
     private static final int DROP_IN_ANIMATION_DURATION = 400;
     private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
@@ -74,39 +99,33 @@
     // The degree to which the outer ring is scaled in its natural state
     private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
 
-    // The amount of vertical spread between items in the stack [0...1]
-    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f;
-
     // Flag as to whether or not to draw an outer ring. Currently none is designed.
     public static final boolean HAS_OUTER_RING = true;
 
     // Flag whether the folder should open itself when an item is dragged over is enabled.
     public static final boolean SPRING_LOADING_ENABLED = true;
 
-    // The degree to which the item in the back of the stack is scaled [0...1]
-    // (0 means it's not scaled at all, 1 means it's scaled to nothing)
-    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
-
     // Delay when drag enters until the folder opens, in miliseconds.
     private static final int ON_OPEN_DELAY = 800;
 
     public static Drawable sSharedFolderLeaveBehind = null;
 
     @Thunk ImageView mPreviewBackground;
-    @Thunk BubbleTextView mFolderName;
+    @Thunk
+    BubbleTextView mFolderName;
 
     FolderRingAnimator mFolderRingAnimator = null;
 
     // These variables are all associated with the drawing of the preview; they are stored
     // as member variables for shared usage and to avoid computation on each frame
     private int mIntrinsicIconSize;
-    private float mBaselineIconScale;
-    private int mBaselineIconSize;
     private int mAvailableSpaceInPreview;
-    private int mTotalWidth = -1;
     private int mPreviewOffsetX;
     private int mPreviewOffsetY;
-    private float mMaxPerspectiveShift;
+    private int mTotalWidth;
+
+    private PreviewLayoutRule mPreviewLayoutRule;
+
     boolean mAnimating = false;
     private Rect mOldBounds = new Rect();
 
@@ -117,7 +136,8 @@
     @Thunk ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
 
     private Alarm mOpenAlarm = new Alarm();
-    @Thunk ItemInfo mDragInfo;
+    @Thunk
+    ItemInfo mDragInfo;
 
     public FolderIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -132,6 +152,7 @@
     private void init() {
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
+        mPreviewLayoutRule = new StackFolderIconLayoutRule();
         setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
     }
 
@@ -142,7 +163,7 @@
         return !workspace.workspaceInModalState();
     }
 
-    static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
+    public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
             FolderInfo folderInfo, IconCache iconCache) {
         @SuppressWarnings("all") // suppress dead code warning
         final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
@@ -196,7 +217,8 @@
     public static class FolderRingAnimator {
         public int mCellX;
         public int mCellY;
-        @Thunk CellLayout mCellLayout;
+        @Thunk
+        CellLayout mCellLayout;
         public float mOuterRingSize;
         public float mInnerRingSize;
         public FolderIcon mFolderIcon = null;
@@ -320,7 +342,7 @@
         return mFolder;
     }
 
-    FolderInfo getFolderInfo() {
+    public FolderInfo getFolderInfo() {
         return mInfo;
     }
 
@@ -453,7 +475,7 @@
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
                       center[1] - animateView.getMeasuredHeight() / 2);
 
-            float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
+            float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
 
             float finalScale = scale * scaleRelativeToDragLayer;
             dragLayer.animateView(animateView, from, to, finalAlpha,
@@ -498,18 +520,12 @@
             final int previewPadding = FolderRingAnimator.sPreviewPadding;
 
             mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
-            // cos(45) = 0.707  + ~= 0.1) = 0.8f
-            int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
-
-            int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
-
-            mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
-
-            mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
-            mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
 
             mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
-            mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
+            mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset + getPaddingTop();
+
+            // Initialize the preview layout rule
+            mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize);
         }
     }
 
@@ -517,7 +533,7 @@
         computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
     }
 
-    class PreviewItemDrawingParams {
+    static class PreviewItemDrawingParams {
         PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
             this.transX = transX;
             this.transY = transY;
@@ -532,7 +548,8 @@
     }
 
     private float getLocalCenterForIndex(int index, int[] center) {
-        mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
+        mParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
+                mParams);
 
         mParams.transX += mPreviewOffsetX;
         mParams.transY += mPreviewOffsetY;
@@ -546,30 +563,7 @@
 
     private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
             PreviewItemDrawingParams params) {
-        index = NUM_ITEMS_IN_PREVIEW - index - 1;
-        float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
-        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
-
-        float offset = (1 - r) * mMaxPerspectiveShift;
-        float scaledSize = scale * mBaselineIconSize;
-        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
-
-        // We want to imagine our coordinates from the bottom left, growing up and to the
-        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
-        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
-        float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
-        float totalScale = mBaselineIconScale * scale;
-        final float overlayAlpha = (80 * (1 - r)) / 255f;
-
-        if (params == null) {
-            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
-        } else {
-            params.transX = transX;
-            params.transY = transY;
-            params.scale = totalScale;
-            params.overlayAlpha = overlayAlpha;
-        }
-        return params;
+        return mPreviewLayoutRule.computePreviewItemDrawingParams(index, params);
     }
 
     private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
@@ -618,7 +612,7 @@
             computePreviewDrawingParams(d);
         }
 
-        int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
+        int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
         if (!mAnimating) {
             for (int i = nItemsInPreview - 1; i >= 0; i--) {
                 v = (TextView) items.get(i);
@@ -646,7 +640,7 @@
         float iconSize = mLauncher.getDeviceProfile().iconSizePx;
         final float scale0 = iconSize / d.getIntrinsicWidth() ;
         final float transX0 = (mAvailableSpaceInPreview - iconSize) / 2;
-        final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2 + getPaddingTop();
+        final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2;
         mAnimParams.drawable = d;
 
         ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
@@ -751,7 +745,15 @@
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
-
         mLongPressHelper.cancelLongPress();
     }
+
+    public interface PreviewLayoutRule {
+        public PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
+            PreviewItemDrawingParams params);
+
+        public void init(int availableSpace, int intrinsicIconSize);
+
+        public int numItems();
+    }
 }
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
similarity index 96%
rename from src/com/android/launcher3/FolderPagedView.java
rename to src/com/android/launcher3/folder/FolderPagedView.java
index 0d1103b..c25444e 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.folder;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -28,8 +28,24 @@
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
 
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
+import com.android.launcher3.FocusIndicatorView;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.PageIndicator;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.util.Thunk;
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
new file mode 100644
index 0000000..1405b40
--- /dev/null
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (C) 2015 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.folder;
+
+import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams;
+
+public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
+
+    public static final int NUM_ITEMS_IN_PREVIEW = 3;
+
+    // The degree to which the item in the back of the stack is scaled [0...1]
+    // (0 means it's not scaled at all, 1 means it's scaled to nothing)
+    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+    // The amount of vertical spread between items in the stack [0...1]
+    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f;
+
+    //private int mIntrinsicIconSize;
+    private float mBaselineIconScale;
+    private int mBaselineIconSize;
+    private int mAvailableSpaceInPreview;
+    private float mMaxPerspectiveShift;
+
+    public void init(int availableSpace, int intrinsicIconSize) {
+        mAvailableSpaceInPreview = availableSpace;
+
+        // cos(45) = 0.707  + ~= 0.1) = 0.8f
+        int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
+
+        int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+
+        mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
+
+        mBaselineIconSize = (int) (intrinsicIconSize * mBaselineIconScale);
+        mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
+    }
+
+    @Override
+    public PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
+            PreviewItemDrawingParams params) {
+
+        index = NUM_ITEMS_IN_PREVIEW - index - 1;
+        float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
+        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
+
+        float offset = (1 - r) * mMaxPerspectiveShift;
+        float scaledSize = scale * mBaselineIconSize;
+        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
+
+        // We want to imagine our coordinates from the bottom left, growing up and to the
+        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
+        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection);
+        float transX = (mAvailableSpaceInPreview - scaledSize) / 2;
+        float totalScale = mBaselineIconScale * scale;
+        final float overlayAlpha = (80 * (1 - r)) / 255f;
+
+        if (params == null) {
+            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
+        } else {
+            params.transX = transX;
+            params.transY = transY;
+            params.scale = totalScale;
+            params.overlayAlpha = overlayAlpha;
+        }
+        return params;
+    }
+
+    @Override
+    public int numItems() {
+        return NUM_ITEMS_IN_PREVIEW;
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index db31756..7ad478e 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Folder;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;