Adding paging for the widget/shortcut/folder customization area and fixing bugs.
Adding pages for customization drawer with initial implementation of proposed flow
layout for widgets.  Fixes for keeping all apps, and widgets in sync with Launcher
Model, optimizations for reloading all apps pages when invalidating.  Adding some
animations for tab transitions and feedback when long pressing to add certain items.

Change-Id: I8d51749f3a91c964bed35681f3a9192200b0d93e
diff --git a/res/anim/paged_view_click_feedback.xml b/res/anim/paged_view_click_feedback.xml
new file mode 100644
index 0000000..786d974
--- /dev/null
+++ b/res/anim/paged_view_click_feedback.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.65"
+    android:duration="100"
+    android:fillAfter="true"
+    android:repeatCount="1"
+    android:repeatMode="reverse" />
diff --git a/res/drawable/default_widget_preview.9.png b/res/drawable/default_widget_preview.9.png
index e966b1b..b3ddada 100644
--- a/res/drawable/default_widget_preview.9.png
+++ b/res/drawable/default_widget_preview.9.png
Binary files differ
diff --git a/res/layout-xlarge/all_apps_paged_view_application.xml b/res/layout-xlarge/all_apps_paged_view_application.xml
index 98c2737..7448765 100644
--- a/res/layout-xlarge/all_apps_paged_view_application.xml
+++ b/res/layout-xlarge/all_apps_paged_view_application.xml
@@ -18,12 +18,13 @@
     android:id="@+id/name"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_gravity="center_horizontal"
+    android:gravity="center_horizontal"
 
     android:textColor="#FFFFFFFF"
     android:shadowColor="#FF000000"
     android:shadowDx="0.0"
     android:shadowDy="1.0"
+    android:shadowRadius="1.0"
 
     android:maxLines="2"
     android:fadingEdge="horizontal" />
diff --git a/res/layout-xlarge/customize_paged_view_item.xml b/res/layout-xlarge/customize_paged_view_item.xml
new file mode 100644
index 0000000..7448765
--- /dev/null
+++ b/res/layout-xlarge/customize_paged_view_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/name"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+
+    android:textColor="#FFFFFFFF"
+    android:shadowColor="#FF000000"
+    android:shadowDx="0.0"
+    android:shadowDy="1.0"
+    android:shadowRadius="1.0"
+
+    android:maxLines="2"
+    android:fadingEdge="horizontal" />
diff --git a/res/layout-xlarge/customize_paged_view_wallpaper_placeholder.xml b/res/layout-xlarge/customize_paged_view_wallpaper_placeholder.xml
new file mode 100644
index 0000000..df73bcd
--- /dev/null
+++ b/res/layout-xlarge/customize_paged_view_wallpaper_placeholder.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/name"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical"
+
+    android:textColor="#FFFFFFFF"
+    android:shadowColor="#FF000000"
+    android:shadowDx="0.0"
+    android:shadowDy="1.0"
+    android:shadowRadius="1.0"
+    android:drawableLeft="@drawable/ic_launcher_wallpaper"
+    android:drawablePadding="10dip"
+
+    android:maxLines="2"
+    android:fadingEdge="horizontal" />
diff --git a/res/layout-xlarge/customize_paged_view_widget.xml b/res/layout-xlarge/customize_paged_view_widget.xml
new file mode 100644
index 0000000..7448765
--- /dev/null
+++ b/res/layout-xlarge/customize_paged_view_widget.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/name"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+
+    android:textColor="#FFFFFFFF"
+    android:shadowColor="#FF000000"
+    android:shadowDx="0.0"
+    android:shadowDy="1.0"
+    android:shadowRadius="1.0"
+
+    android:maxLines="2"
+    android:fadingEdge="horizontal" />
diff --git a/res/layout-xlarge/launcher.xml b/res/layout-xlarge/launcher.xml
index a1879ef..3e00381 100644
--- a/res/layout-xlarge/launcher.xml
+++ b/res/layout-xlarge/launcher.xml
@@ -26,7 +26,7 @@
         layout="@layout/all_apps_tabbed"
         android:id="@+id/all_apps_view"
         android:layout_width="match_parent"
-        android:layout_height="500dip"
+        android:layout_height="550dip"
         android:layout_gravity="top"/>
 
     <!-- The workspace contains 5 screens of cells -->
@@ -112,38 +112,24 @@
     <TabHost
         android:id="@android:id/tabhost"
         android:layout_width="match_parent"
-        android:layout_height="500dip"
+        android:layout_height="550dip"
         android:layout_gravity="bottom">
         <LinearLayout
             android:orientation="vertical"
+            android:background="#40000000"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
             <TabWidget
                 android:id="@android:id/tabs"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:tabStripEnabled="false"
+                android:paddingBottom="10dp" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
-                android:background="#ff000000"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent">
-                <com.android.launcher2.WidgetChooser
-                    android:id="@+id/widget_chooser"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent" />
-                    <com.android.launcher2.FolderChooser
-                        android:id="@+id/folder_chooser"
-                        android:layout_width="match_parent"
-                        android:layout_height="match_parent" />
-                    <com.android.launcher2.ShortcutChooser
-                        android:id="@+id/shortcut_chooser"
-                        android:layout_width="match_parent"
-                        android:layout_height="match_parent" />
-                    <TextView
-                        android:id="@+id/wallpaperstab"
-                        android:layout_width="match_parent"
-                        android:layout_height="match_parent"
-                        android:text="@string/wallpapers_temp_tab_text" />
              </FrameLayout>
           </LinearLayout>
     </TabHost>
diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java
index e0d248e..4e81937 100644
--- a/src/com/android/launcher2/AllAppsPagedView.java
+++ b/src/com/android/launcher2/AllAppsPagedView.java
@@ -20,6 +20,10 @@
 import java.util.Collections;
 import java.util.Comparator;
 
+import android.animation.Animatable;
+import android.animation.AnimatableListenerAdapter;
+import android.animation.Animator;
+import android.animation.PropertyAnimator;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -98,9 +102,11 @@
 
     public void setAppFilter(int filterType) {
         mAppFilter = filterType;
-        mFilteredApps = rebuildFilteredApps(mApps);
-        setCurrentScreen(0);
-        invalidatePageData();
+        if (mApps != null) {
+            mFilteredApps = rebuildFilteredApps(mApps);
+            setCurrentScreen(0);
+            invalidatePageData();
+        }
     }
 
     @Override
@@ -156,22 +162,13 @@
         if (childIndex == getCurrentScreen()) {
             final ApplicationInfo app = (ApplicationInfo) v.getTag();
 
-            AlphaAnimation anim = new AlphaAnimation(1.0f, 0.65f);
-            anim.setDuration(100);
-            anim.setFillAfter(true);
-            anim.setRepeatMode(AlphaAnimation.REVERSE);
-            anim.setRepeatCount(1);
-            anim.setAnimationListener(new AnimationListener() {
+            // animate some feedback to the click
+            animateClickFeedback(v, new Runnable() {
                 @Override
-                public void onAnimationStart(Animation animation) {}
-                @Override
-                public void onAnimationRepeat(Animation animation) {
+                public void run() {
                     mLauncher.startActivitySafely(app.intent, app);
                 }
-                @Override
-                public void onAnimationEnd(Animation animation) {}
             });
-            v.startAnimation(anim);
         }
     }
 
@@ -223,26 +220,30 @@
     @Override
     public void setApps(ArrayList<ApplicationInfo> list) {
         mApps = list;
-        Collections.sort(mApps, new Comparator<ApplicationInfo>() {
-            @Override
-            public int compare(ApplicationInfo object1, ApplicationInfo object2) {
-                return object1.title.toString().compareTo(object2.title.toString());
-            }
-        });
+        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
         mFilteredApps = rebuildFilteredApps(mApps);
         invalidatePageData();
     }
 
+    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+        // we add it in place, in alphabetical order
+        final int count = list.size();
+        for (int i = 0; i < count; ++i) {
+            final ApplicationInfo info = list.get(i);
+            final int index = Collections.binarySearch(mApps, info, LauncherModel.APP_NAME_COMPARATOR);
+            if (index < 0) {
+                mApps.add(-(index + 1), info);
+            }
+        }
+        mFilteredApps = rebuildFilteredApps(mApps);
+    }
     @Override
     public void addApps(ArrayList<ApplicationInfo> list) {
-        // TODO: we need to add it in place, in alphabetical order
-        mApps.addAll(list);
-        mFilteredApps.addAll(rebuildFilteredApps(list));
+        addAppsWithoutInvalidate(list);
         invalidatePageData();
     }
 
-    @Override
-    public void removeApps(ArrayList<ApplicationInfo> list) {
+    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
         // loop through all the apps and remove apps that have the same component
         final int length = list.size();
         for (int i = 0; i < length; ++i) {
@@ -251,14 +252,19 @@
                 mApps.remove(removeIndex);
             }
         }
-        mFilteredApps = rebuildFilteredApps(list);
+        mFilteredApps = rebuildFilteredApps(mApps);
+    }
+    @Override
+    public void removeApps(ArrayList<ApplicationInfo> list) {
+        removeAppsWithoutInvalidate(list);
         invalidatePageData();
     }
 
     @Override
     public void updateApps(ArrayList<ApplicationInfo> list) {
-        removeApps(list);
-        addApps(list);
+        removeAppsWithoutInvalidate(list);
+        addAppsWithoutInvalidate(list);
+        invalidatePageData();
     }
 
     private int findAppByComponent(ArrayList<ApplicationInfo> list, ApplicationInfo item) {
@@ -308,25 +314,43 @@
     @Override
     public void syncPageItems(int page) {
         // ensure that we have the right number of items on the pages
-        int numCells = mCellCountX * mCellCountY;
-        int startIndex = page * numCells;
-        int endIndex = Math.min(startIndex + numCells, mFilteredApps.size());
+        final int cellsPerPage = mCellCountX * mCellCountY;
+        final int startIndex = page * cellsPerPage;
+        final int endIndex = Math.min(startIndex + cellsPerPage, mFilteredApps.size());
         PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
-        // TODO: we can optimize by just re-applying to existing views
-        layout.removeAllViews();
-        for (int i = startIndex; i < endIndex; ++i) {
-            ApplicationInfo info = mFilteredApps.get(i);
+
+        final int curNumPageItems = layout.getChildCount();
+        final int numPageItems = endIndex - startIndex;
+
+        // remove any extra items
+        int extraPageItemsDiff = curNumPageItems - numPageItems;
+        for (int i = 0; i < extraPageItemsDiff; ++i) {
+            layout.removeViewAt(numPageItems);
+        }
+        // add any necessary items
+        for (int i = curNumPageItems; i < numPageItems; ++i) {
             TextView text = (TextView) mInflater.inflate(R.layout.all_apps_paged_view_application, layout, false);
+            text.setOnClickListener(this);
+            text.setOnLongClickListener(this);
+
+            layout.addViewToCellLayout(text, -1, i,
+                new PagedViewCellLayout.LayoutParams(0, 0, 1, 1));
+        }
+
+        // actually reapply to the existing text views
+        for (int i = startIndex; i < endIndex; ++i) {
+            int index = i - startIndex;
+            ApplicationInfo info = mFilteredApps.get(i);
+            TextView text = (TextView) layout.getChildAt(index);
             text.setCompoundDrawablesWithIntrinsicBounds(null,
                 new BitmapDrawable(info.iconBitmap), null, null);
             text.setText(info.title);
             text.setTag(info);
-            text.setOnClickListener(this);
-            text.setOnLongClickListener(this);
 
-            int index = i - startIndex;
-            layout.addViewToCellLayout(text, index, i,
-                new PagedViewCellLayout.LayoutParams(index % mCellCountX, index / mCellCountX, 1, 1));
+            PagedViewCellLayout.LayoutParams params = 
+                (PagedViewCellLayout.LayoutParams) text.getLayoutParams();
+            params.cellX = index % mCellCountX;
+            params.cellY = index / mCellCountX;
         }
     }
 
diff --git a/src/com/android/launcher2/AllAppsTabbed.java b/src/com/android/launcher2/AllAppsTabbed.java
index 0470cee..114da4e 100644
--- a/src/com/android/launcher2/AllAppsTabbed.java
+++ b/src/com/android/launcher2/AllAppsTabbed.java
@@ -18,6 +18,10 @@
 
 import java.util.ArrayList;
 
+import android.animation.Animatable;
+import android.animation.AnimatableListenerAdapter;
+import android.animation.Animator;
+import android.animation.PropertyAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -96,16 +100,30 @@
 
         setOnTabChangedListener(new OnTabChangeListener() {
             public void onTabChanged(String tabId) {
-                String tag = getCurrentTabTag();
-                if (tag == TAG_ALL) {
-                    mAllApps.setAppFilter(AllAppsPagedView.ALL_APPS_FLAG);
-                } else if (tag == TAG_APPS) {
-                    mAllApps.setAppFilter(ApplicationInfo.APP_FLAG);
-                } else if (tag == TAG_GAMES) {
-                    mAllApps.setAppFilter(ApplicationInfo.GAME_FLAG);
-                } else if (tag == TAG_DOWNLOADED) {
-                    mAllApps.setAppFilter(ApplicationInfo.DOWNLOADED_FLAG);
-                }
+                // animate the changing of the tab content by fading pages in and out
+                final int duration = 150;
+                final float alpha = mAllApps.getAlpha();
+                Animator alphaAnim = new PropertyAnimator(duration, mAllApps, "alpha", alpha, 0.0f);
+                alphaAnim.addListener(new AnimatableListenerAdapter() {
+                    public void onAnimationEnd(Animatable animation) {
+                        String tag = getCurrentTabTag();
+                        if (tag == TAG_ALL) {
+                            mAllApps.setAppFilter(AllAppsPagedView.ALL_APPS_FLAG);
+                        } else if (tag == TAG_APPS) {
+                            mAllApps.setAppFilter(ApplicationInfo.APP_FLAG);
+                        } else if (tag == TAG_GAMES) {
+                            mAllApps.setAppFilter(ApplicationInfo.GAME_FLAG);
+                        } else if (tag == TAG_DOWNLOADED) {
+                            mAllApps.setAppFilter(ApplicationInfo.DOWNLOADED_FLAG);
+                        }
+
+                        final float alpha = mAllApps.getAlpha();
+                        Animator alphaAnim = 
+                            new PropertyAnimator(duration, mAllApps, "alpha", alpha, 1.0f);
+                        alphaAnim.start();
+                    }
+                });
+                alphaAnim.start();
             }
         });
 
@@ -135,9 +153,12 @@
 
     @Override
     public void setVisibility(int visibility) {
+        final boolean isVisible = (visibility == View.VISIBLE); 
         super.setVisibility(visibility);
-        float zoom = visibility == View.VISIBLE ? 1.0f : 0.0f;
+        float zoom = (isVisible ? 1.0f : 0.0f);
         mAllApps.zoom(zoom, false);
+        if (!isVisible)
+            mAllApps.cleanup();
     }
 
     @Override
diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java
new file mode 100644
index 0000000..7679e39
--- /dev/null
+++ b/src/com/android/launcher2/CustomizePagedView.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2010 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.Region.Op;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.provider.LiveFolders;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.launcher.R;
+
+public class CustomizePagedView extends PagedView
+    implements View.OnLongClickListener,
+                DragSource {
+
+    public enum CustomizationType {
+        WidgetCustomization,
+        FolderCustomization,
+        ShortcutCustomization,
+        WallpaperCustomization
+    }
+
+    private static final String TAG = "CustomizeWorkspace";
+    private static final boolean DEBUG = false;
+
+    private Launcher mLauncher;
+    private DragController mDragController;
+    private PackageManager mPackageManager;
+
+    private CustomizationType mCustomizationType;
+
+    private PagedViewCellLayout mTmpWidgetLayout;
+    private ArrayList<ArrayList<PagedViewCellLayout.LayoutParams>> mWidgetPages;
+    private List<AppWidgetProviderInfo> mWidgetList;
+    private List<ResolveInfo> mFolderList;
+    private List<ResolveInfo> mShortcutList;
+
+    private int mCellCountX;
+    private int mCellCountY;
+
+    private final Canvas mCanvas = new Canvas();
+    private final LayoutInflater mInflater;
+
+    public CustomizePagedView(Context context) {
+        this(context, null);
+    }
+
+    public CustomizePagedView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCellCountX = 8;
+        mCellCountY = 4;
+        mCustomizationType = CustomizationType.WidgetCustomization;
+        mWidgetPages = new ArrayList<ArrayList<PagedViewCellLayout.LayoutParams>>();
+        mTmpWidgetLayout = new PagedViewCellLayout(context);
+        mInflater = LayoutInflater.from(context);
+        setupPage(mTmpWidgetLayout);
+        setVisibility(View.GONE);
+        setSoundEffectsEnabled(false);
+    }
+
+    public void setLauncher(Launcher launcher) {
+        Context context = getContext();
+        mLauncher = launcher;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public void update() {
+        Context context = getContext();
+
+        // get the list of widgets
+        mWidgetList = AppWidgetManager.getInstance(mLauncher).getInstalledProviders();
+        Collections.sort(mWidgetList, new Comparator<AppWidgetProviderInfo>() {
+            @Override
+            public int compare(AppWidgetProviderInfo object1, AppWidgetProviderInfo object2) {
+                return object1.label.compareTo(object2.label);
+            }
+        });
+
+        Comparator<ResolveInfo> resolveInfoComparator = new Comparator<ResolveInfo>() {
+            @Override
+            public int compare(ResolveInfo object1, ResolveInfo object2) {
+                return object1.loadLabel(mPackageManager).toString().compareTo(
+                        object2.loadLabel(mPackageManager).toString());
+            }
+        };
+
+        // get the list of live folder intents
+        Intent liveFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
+        mFolderList = mPackageManager.queryIntentActivities(liveFolderIntent, 0);
+
+        // manually create a separate entry for creating a folder in Launcher
+        ResolveInfo folder = new ResolveInfo();
+        folder.icon = R.drawable.ic_launcher_folder;
+        folder.labelRes = R.string.group_folder;
+        folder.resolvePackageName = context.getPackageName();
+        mFolderList.add(0, folder);
+        Collections.sort(mFolderList, resolveInfoComparator);
+
+        // get the list of shortcuts
+        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+        mShortcutList = mPackageManager.queryIntentActivities(shortcutsIntent, 0);
+        Collections.sort(mShortcutList, resolveInfoComparator);
+
+        invalidatePageData();
+    }
+
+    public void setDragController(DragController dragger) {
+        mDragController = dragger;
+    }
+
+    public void setCustomizationFilter(CustomizationType filterType) {
+        mCustomizationType = filterType;
+        setCurrentScreen(0);
+        invalidatePageData();
+    }
+
+    @Override
+    public void onDropCompleted(View target, boolean success) {
+        // do nothing
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        if (!v.isInTouchMode()) {
+            return false;
+        }
+
+        final View animView = v;
+        switch (mCustomizationType) {
+        case WidgetCustomization:
+            AppWidgetProviderInfo appWidgetInfo = (AppWidgetProviderInfo) v.getTag();
+            LauncherAppWidgetInfo dragInfo = new LauncherAppWidgetInfo(appWidgetInfo.provider);
+            dragInfo.minWidth = appWidgetInfo.minWidth;
+            dragInfo.minHeight = appWidgetInfo.minHeight;
+            mDragController.startDrag(v, this, dragInfo, DragController.DRAG_ACTION_COPY);
+            mLauncher.hideCustomizationDrawer();
+            return true;
+        case FolderCustomization:
+            // animate some feedback to the long press
+            animateClickFeedback(v, new Runnable() {
+                @Override
+                public void run() {
+                    // add the folder
+                    ResolveInfo resolveInfo = (ResolveInfo) animView.getTag();
+                    Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
+                    if (resolveInfo.labelRes == R.string.group_folder) {
+                        // Create app shortcuts is a special built-in case of shortcuts
+                        createFolderIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
+                            getContext().getString(R.string.group_folder));
+                    } else {
+                        ComponentName name = new ComponentName(resolveInfo.activityInfo.packageName,
+                                resolveInfo.activityInfo.name);
+                        createFolderIntent.setComponent(name);
+                    }
+                    mLauncher.prepareAddItemFromHomeCustomizationDrawer();
+                    mLauncher.addLiveFolder(createFolderIntent);
+                }
+            });
+            return true;
+        case ShortcutCustomization:
+            // animate some feedback to the long press
+            animateClickFeedback(v, new Runnable() {
+                @Override
+                public void run() {
+                    // add the shortcut
+                    ResolveInfo info = (ResolveInfo) animView.getTag();
+                    Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                    if (info.labelRes == R.string.group_applications) {
+                        // Create app shortcuts is a special built-in case of shortcuts
+                        createShortcutIntent.putExtra(
+                                Intent.EXTRA_SHORTCUT_NAME,getContext().getString(
+                                        R.string.group_applications));
+                    } else {
+                        ComponentName name = new ComponentName(info.activityInfo.packageName, 
+                                info.activityInfo.name);
+                        createShortcutIntent.setComponent(name);
+                    }
+                    mLauncher.prepareAddItemFromHomeCustomizationDrawer();
+                    mLauncher.processShortcut(createShortcutIntent);
+                }
+            });
+            return true;
+        }
+        return false;
+    }
+
+    private int relayoutWidgets() {
+        final int widgetCount = mWidgetList.size();
+        if (widgetCount == 0) return 0;
+
+        mWidgetPages.clear();
+        ArrayList<PagedViewCellLayout.LayoutParams> page = 
+            new ArrayList<PagedViewCellLayout.LayoutParams>();
+        mWidgetPages.add(page);
+        int rowOffsetX = 0;
+        int rowOffsetY = 0;
+        int curRowHeight = 0;
+        // we only get the cell dims this way for the layout calculations because
+        // we know that we aren't going to change the dims when we construct it
+        // afterwards
+        for (int i = 0; i < widgetCount; ++i) {
+            AppWidgetProviderInfo info = mWidgetList.get(i);
+            PagedViewCellLayout.LayoutParams params;
+
+            final int cellSpanX = mTmpWidgetLayout.estimateCellHSpan(info.minWidth);
+            final int cellSpanY = mTmpWidgetLayout.estimateCellVSpan(info.minHeight);
+
+            if (((rowOffsetX + cellSpanX) <= mCellCountX) &&
+                    ((rowOffsetY + cellSpanY) <= mCellCountY)) {
+                // just add to end of current row
+                params = new PagedViewCellLayout.LayoutParams(rowOffsetX, rowOffsetY,
+                        cellSpanX, cellSpanY);
+
+                rowOffsetX += cellSpanX;
+                curRowHeight = Math.max(curRowHeight, cellSpanY);
+            } else {
+                /*
+                // fix all the items in this last row to be bottom aligned
+                int prevRowOffsetX = rowOffsetX;
+                for (int j = page.size() - 1; j >= 0; --j) {
+                    PagedViewCellLayout.LayoutParams params = page.get(j);
+                    // skip once we get to the previous row
+                    if (params.cellX > prevRowOffsetX)
+                        break;
+
+                    params.cellY += curRowHeight - params.cellVSpan;
+                    prevRowOffsetX = params.cellX;
+                }
+                */
+
+                // doesn't fit on current row, see if we can start a new row on
+                // this page
+                if ((rowOffsetY + curRowHeight + cellSpanY) > mCellCountY) {
+                    // start a new page and add this item to it
+                    page = new ArrayList<PagedViewCellLayout.LayoutParams>();
+                    mWidgetPages.add(page);
+
+                    params = new PagedViewCellLayout.LayoutParams(0, 0, cellSpanX, cellSpanY);
+                    rowOffsetX = cellSpanX;
+                    rowOffsetY = 0;
+                    curRowHeight = cellSpanY;
+                } else {
+                    // add it to the current page on this new row
+                    params = new PagedViewCellLayout.LayoutParams(0, rowOffsetY + curRowHeight,
+                            cellSpanX, cellSpanY);
+
+                    rowOffsetX = cellSpanX;
+                    rowOffsetY += curRowHeight;
+                    curRowHeight = cellSpanY;
+                }
+            }
+
+            params.setTag(info);
+            page.add(params);
+        }
+        return mWidgetPages.size();
+    }
+
+    private Drawable getWidgetIcon(PagedViewCellLayout.LayoutParams params, 
+            AppWidgetProviderInfo info) {
+        PackageManager packageManager = mLauncher.getPackageManager();
+        String packageName = info.provider.getPackageName();
+        Drawable drawable = null;
+        if (info.previewImage != 0) {
+            drawable = packageManager.getDrawable(packageName, info.previewImage, null);
+            if (drawable == null) {
+                Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+                        + " for provider: " + info.provider);
+            } else {
+                return drawable;
+            }
+        }
+
+        // If we don't have a preview image, create a default one
+        if (drawable == null) {
+            Resources resources = mLauncher.getResources();
+
+            // Determine the size the widget will take in the layout
+            // Create a new bitmap to hold the widget preview
+            int[] dims = mTmpWidgetLayout.estimateCellDimensions(getMeasuredWidth(), 
+                    getMeasuredHeight(), params.cellHSpan, params.cellVSpan);
+            final int width = dims[0];
+            final int height = dims[1] - 35;
+            // TEMP
+            // TEMP: HACK ABOVE TO GET TEXT TO SHOW
+            // TEMP
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+            mCanvas.setBitmap(bitmap);
+            // For some reason, we must re-set the clip rect here, otherwise it will be wrong
+            mCanvas.clipRect(0, 0, width, height, Op.REPLACE);
+
+            Drawable background = resources.getDrawable(R.drawable.default_widget_preview);
+            background.setBounds(0, 0, width, height);
+            background.draw(mCanvas);
+
+            // Draw the icon vertically centered, flush left
+            try {
+                Rect tmpRect = new Rect();
+                Drawable icon = null;
+                if (info.icon != 0) {
+                    icon = packageManager.getDrawable(packageName, info.icon, null);
+                } else {
+                    icon = resources.getDrawable(R.drawable.ic_launcher_application);
+                }
+                background.getPadding(tmpRect);
+
+                final int iconSize = Math.min(
+                        Math.min(icon.getIntrinsicWidth(), width - tmpRect.left - tmpRect.right),
+                        Math.min(icon.getIntrinsicHeight(), height - tmpRect.top - tmpRect.bottom));
+                final int left = (width / 2) - (iconSize / 2);
+                final int top = (height / 2) - (iconSize / 2);
+                icon.setBounds(new Rect(left, top, left + iconSize, top + iconSize));
+                icon.draw(mCanvas);
+            } catch (Resources.NotFoundException e) {
+                // if we can't find the icon, then just don't draw it
+            }
+
+            drawable = new BitmapDrawable(resources, bitmap);
+        }
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        return drawable;
+    }
+
+    private void setupPage(PagedViewCellLayout layout) {
+        layout.setCellCount(mCellCountX, mCellCountY);
+        layout.setPadding(20, 10, 20, 0);
+    }
+
+    private void syncWidgetPages() {
+        if (mWidgetList == null) return;
+
+        // calculate the layout for all the widget pages first and ensure that
+        // we have the right number of pages
+        int numPages = relayoutWidgets();
+        int curNumPages = getChildCount();
+        // remove any extra pages after the "last" page
+        int extraPageDiff = curNumPages - numPages;
+        for (int i = 0; i < extraPageDiff; ++i) {
+            removeViewAt(numPages);
+        }
+        // add any necessary pages
+        for (int i = curNumPages; i < numPages; ++i) {
+            PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+            setupPage(layout);
+            addView(layout);
+        }
+    }
+
+    private void syncWidgetPageItems(int page) {
+        // ensure that we have the right number of items on the pages
+        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+        final ArrayList<PagedViewCellLayout.LayoutParams> list = mWidgetPages.get(page);
+        final int count = list.size();
+        layout.removeAllViews();
+        for (int i = 0; i < count; ++i) {
+            PagedViewCellLayout.LayoutParams params = list.get(i);
+            AppWidgetProviderInfo info = (AppWidgetProviderInfo) params.getTag();
+            final Drawable icon = getWidgetIcon(params, info);
+            TextView text = (TextView) mInflater.inflate(R.layout.customize_paged_view_widget, 
+                    layout, false);
+            text.setCompoundDrawablesWithIntrinsicBounds(null, icon, null, null);
+            text.setText(info.label);
+            text.setTag(info);
+            text.setOnLongClickListener(this);
+
+            layout.addViewToCellLayout(text, -1, mWidgetList.indexOf(info), params);
+        }
+    }
+
+    private void syncListPages(List<ResolveInfo> list) {
+        // ensure that we have the right number of pages
+        int numPages = (int) Math.ceil((float) list.size() / (mCellCountX * mCellCountY));
+        int curNumPages = getChildCount();
+        // remove any extra pages after the "last" page
+        int extraPageDiff = curNumPages - numPages;
+        for (int i = 0; i < extraPageDiff; ++i) {
+            removeViewAt(numPages);
+        }
+        // add any necessary pages
+        for (int i = curNumPages; i < numPages; ++i) {
+            PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+            setupPage(layout);
+            addView(layout);
+        }
+    }
+
+    private void syncListPageItems(int page, List<ResolveInfo> list) {
+        // ensure that we have the right number of items on the pages
+        int numCells = mCellCountX * mCellCountY;
+        int startIndex = page * numCells;
+        int endIndex = Math.min(startIndex + numCells, list.size());
+        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+        // TODO: we can optimize by just re-applying to existing views
+        layout.removeAllViews();
+        for (int i = startIndex; i < endIndex; ++i) {
+            ResolveInfo info = list.get(i);
+            Drawable image = info.loadIcon(mPackageManager);
+            TextView text = (TextView) mInflater.inflate(R.layout.customize_paged_view_item, 
+                    layout, false);
+            image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
+            text.setCompoundDrawablesWithIntrinsicBounds(null, image, null, null);
+            text.setText(info.loadLabel(mPackageManager));
+            text.setTag(info);
+            text.setOnLongClickListener(this);
+
+            final int index = i - startIndex;
+            final int x = index % mCellCountX;
+            final int y = index / mCellCountX;
+            layout.addViewToCellLayout(text, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1));
+        }
+    }
+
+    private void syncWallpaperPages() {
+        // NOT CURRENTLY IMPLEMENTED
+        // ensure that we have the right number of pages
+        int numPages = 1;
+        int curNumPages = getChildCount();
+        // remove any extra pages after the "last" page
+        int extraPageDiff = curNumPages - numPages;
+        for (int i = 0; i < extraPageDiff; ++i) {
+            removeViewAt(numPages);
+        }
+        // add any necessary pages
+        for (int i = curNumPages; i < numPages; ++i) {
+            PagedViewCellLayout layout = new PagedViewCellLayout(getContext());
+            setupPage(layout);
+            addView(layout);
+        }
+    }
+
+    private void syncWallpaperPageItems(int page) {
+        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(page);
+        layout.removeAllViews();
+
+        TextView text = (TextView) mInflater.inflate(
+                R.layout.customize_paged_view_wallpaper_placeholder, layout, false);
+        // NOTE: this is just place holder text until MikeJurka implements wallpaper picker
+        text.setText("Wallpaper customization coming soon!");
+
+        layout.addViewToCellLayout(text, -1, 0, new PagedViewCellLayout.LayoutParams(0, 0, 3, 1));
+    }
+
+    @Override
+    public void syncPages() {
+        switch (mCustomizationType) {
+        case WidgetCustomization:
+            syncWidgetPages();
+            break;
+        case FolderCustomization:
+            syncListPages(mFolderList);
+            break;
+        case ShortcutCustomization:
+            syncListPages(mShortcutList);
+            break;
+        case WallpaperCustomization:
+            syncWallpaperPages();
+            break;
+        default:
+            removeAllViews();
+            setCurrentScreen(0);
+            break;
+        }
+
+        // only try and center the page if there is one page
+        final int childCount = getChildCount();
+        if (childCount == 1) {
+            PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(0);
+            layout.enableCenteredContent(true);
+        } else {
+            for (int i = 0; i < childCount; ++i) {
+                PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+                layout.enableCenteredContent(false);
+            }
+        }
+
+        // bound the current page
+        setCurrentScreen(Math.max(0, Math.min(childCount - 1, getCurrentScreen())));
+    }
+
+    @Override
+    public void syncPageItems(int page) {
+        switch (mCustomizationType) {
+        case WidgetCustomization:
+            syncWidgetPageItems(page);
+            break;
+        case FolderCustomization:
+            syncListPageItems(page, mFolderList);
+            break;
+        case ShortcutCustomization:
+            syncListPageItems(page, mShortcutList);
+            break;
+        case WallpaperCustomization:
+            syncWallpaperPageItems(page);
+            break;
+        }
+    }
+}
diff --git a/src/com/android/launcher2/FolderChooser.java b/src/com/android/launcher2/FolderChooser.java
deleted file mode 100644
index b152ad5..0000000
--- a/src/com/android/launcher2/FolderChooser.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.launcher2;
-
-import com.android.launcher.R;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.provider.LiveFolders;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.AdapterView;
-
-public class FolderChooser extends HomeCustomizationItemGallery {
-
-    public FolderChooser(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-        // todo: this code sorta overlaps with other places
-        ResolveInfo info = (ResolveInfo)getAdapter().getItem(position);
-        mLauncher.prepareAddItemFromHomeCustomizationDrawer();
-
-        Intent createFolderIntent = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
-        if (info.labelRes == R.string.group_folder) {
-            // Create app shortcuts is a special built-in case of shortcuts
-            createFolderIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getContext().getString(R.string.group_folder));
-        } else {
-            ComponentName name = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
-            createFolderIntent.setComponent(name);
-        }
-        mLauncher.addLiveFolder(createFolderIntent);
-
-        return true;
-    }
-}
diff --git a/src/com/android/launcher2/HomeCustomizationItemGallery.java b/src/com/android/launcher2/HomeCustomizationItemGallery.java
deleted file mode 100644
index 2eba49f..0000000
--- a/src/com/android/launcher2/HomeCustomizationItemGallery.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2010 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.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.Gallery;
-
-public abstract class HomeCustomizationItemGallery extends Gallery
-    implements Gallery.OnItemLongClickListener {
-
-    protected Context mContext;
-
-    protected Launcher mLauncher;
-
-    protected int mMotionDownRawX;
-    protected int mMotionDownRawY;
-
-    public HomeCustomizationItemGallery(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setLongClickable(true);
-        setOnItemLongClickListener(this);
-        mContext = context;
-
-        setCallbackDuringFling(false);
-    }
-
-    public void setLauncher(Launcher launcher) {
-        mLauncher = launcher;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN && mLauncher.isAllAppsVisible()) {
-            return false;
-        }
-
-        super.onTouchEvent(ev);
-
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        switch (ev.getAction()) {
-        case MotionEvent.ACTION_DOWN:
-            mMotionDownRawX = (int) ev.getRawX();
-            mMotionDownRawY = (int) ev.getRawY();
-        }
-        return true;
-    }
-}
-
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index ccd6f65..41f9e7d 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -16,8 +16,13 @@
 
 package com.android.launcher2;
 
-import com.android.common.Search;
-import com.android.launcher.R;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 import android.animation.Animatable;
 import android.animation.AnimatableListenerAdapter;
@@ -39,8 +44,8 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
+import android.content.Intent.ShortcutIconResource;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -50,6 +55,7 @@
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -73,10 +79,12 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
+import android.view.View.OnLongClickListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -84,17 +92,16 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
+import android.widget.RelativeLayout;
 import android.widget.TabHost;
+import android.widget.TabWidget;
 import android.widget.TextView;
 import android.widget.Toast;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+import com.android.common.Search;
+import com.android.launcher.R;
 
 /**
  * Default launcher application.
@@ -166,6 +173,12 @@
     // Type: long
     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
 
+    // tags for the customization tabs
+    private static final String WIDGETS_TAG = "widgets";
+    private static final String FOLDERS_TAG = "folders";
+    private static final String SHORTCUTS_TAG = "shortcuts";
+    private static final String WALLPAPERS_TAG = "wallpapers";
+
     static final int APPWIDGET_HOST_ID = 1024;
 
     private static final Object sLock = new Object();
@@ -192,6 +205,7 @@
     private HandleView mHandleView;
     private AllAppsView mAllAppsGrid;
     private TabHost mHomeCustomizationDrawer;
+    private CustomizePagedView mCustomizePagedView;
 
     private Bundle mSavedState;
 
@@ -252,18 +266,72 @@
         if (mHomeCustomizationDrawer != null) {
             mHomeCustomizationDrawer.setup();
 
+            // share the same customization workspace across all the tabs
+            mCustomizePagedView = new CustomizePagedView(this);
+            TabContentFactory contentFactory = new TabContentFactory() {
+                public View createTabContent(String tag) {
+                    return mCustomizePagedView;
+                }
+            };
+
             String widgetsLabel = getString(R.string.widgets_tab_label);
-            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec("widgets")
-                    .setIndicator(widgetsLabel).setContent(R.id.widget_chooser));
+            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WIDGETS_TAG)
+                    .setIndicator(widgetsLabel).setContent(contentFactory));
             String foldersLabel = getString(R.string.folders_tab_label);
-            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec("folders")
-                    .setIndicator(foldersLabel).setContent(R.id.folder_chooser));
+            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(FOLDERS_TAG)
+                    .setIndicator(foldersLabel).setContent(contentFactory));
             String shortcutsLabel = getString(R.string.shortcuts_tab_label);
-            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec("shortcuts")
-                    .setIndicator(shortcutsLabel).setContent(R.id.shortcut_chooser));
+            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(SHORTCUTS_TAG)
+                    .setIndicator(shortcutsLabel).setContent(contentFactory));
             String wallpapersLabel = getString(R.string.wallpapers_tab_label);
-            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec("wallpapers")
-                    .setIndicator(wallpapersLabel).setContent(R.id.wallpaperstab));
+            mHomeCustomizationDrawer.addTab(mHomeCustomizationDrawer.newTabSpec(WALLPAPERS_TAG)
+                    .setIndicator(wallpapersLabel).setContent(contentFactory));
+
+            // TEMP: just styling the tab widget to be a bit nicer until we get the actual
+            // new assets
+            TabWidget tabWidget = mHomeCustomizationDrawer.getTabWidget();
+            for (int i = 0; i < tabWidget.getChildCount(); ++i) {
+                RelativeLayout tab = (RelativeLayout) tabWidget.getChildTabViewAt(i);
+                TextView text = (TextView) tab.getChildAt(1);
+                text.setTextSize(20.0f);
+                text.setPadding(20, 0, 20, 0);
+                text.setShadowLayer(1.0f, 0.0f, 1.0f, Color.BLACK);
+                tab.setBackgroundDrawable(null);
+            }
+
+            mHomeCustomizationDrawer.setOnTabChangedListener(new OnTabChangeListener() {
+                public void onTabChanged(String tabId) {
+                    // animate the changing of the tab content by fading pages in and out
+                    final int duration = 150;
+                    final float alpha = mCustomizePagedView.getAlpha();
+                    Animator alphaAnim = new PropertyAnimator(duration, mCustomizePagedView, 
+                            "alpha", alpha, 0.0f);
+                    alphaAnim.addListener(new AnimatableListenerAdapter() {
+                        public void onAnimationEnd(Animatable animation) {
+                            String tag = mHomeCustomizationDrawer.getCurrentTabTag();
+                            if (tag == WIDGETS_TAG) {
+                                mCustomizePagedView.setCustomizationFilter(
+                                    CustomizePagedView.CustomizationType.WidgetCustomization);
+                            } else if (tag == FOLDERS_TAG) {
+                                mCustomizePagedView.setCustomizationFilter(
+                                    CustomizePagedView.CustomizationType.FolderCustomization);
+                            } else if (tag == SHORTCUTS_TAG) {
+                                mCustomizePagedView.setCustomizationFilter(
+                                    CustomizePagedView.CustomizationType.ShortcutCustomization);
+                            } else if (tag == WALLPAPERS_TAG) {
+                                mCustomizePagedView.setCustomizationFilter(
+                                    CustomizePagedView.CustomizationType.WallpaperCustomization);
+                            }
+
+                            final float alpha = mCustomizePagedView.getAlpha();
+                            Animator alphaAnim = new PropertyAnimator(duration, mCustomizePagedView, 
+                                    "alpha", alpha, 1.0f);
+                            alphaAnim.start();
+                        }
+                    });
+                    alphaAnim.start();
+                }
+            });
     
             mHomeCustomizationDrawer.setCurrentTab(0);
         }
@@ -776,24 +844,10 @@
             mHandleView.setOnLongClickListener(this);
         }
 
-        WidgetChooser widgetChooser = (WidgetChooser) findViewById(R.id.widget_chooser);
-        if (widgetChooser != null) {
-            WidgetListAdapter widgetGalleryAdapter = new WidgetListAdapter(this);
-            widgetChooser.setAdapter(widgetGalleryAdapter);
-            widgetChooser.setDragController(dragController);
-            widgetChooser.setLauncher(this);
-
-            FolderChooser folderChooser = (FolderChooser) findViewById(R.id.folder_chooser);
-            IntentListAdapter folderTypes = new FolderListAdapter(
-                    this, LiveFolders.ACTION_CREATE_LIVE_FOLDER);
-            folderChooser.setAdapter(folderTypes);
-            folderChooser.setLauncher(this);
-
-            ShortcutChooser shortcutChooser = (ShortcutChooser) findViewById(R.id.shortcut_chooser);
-            IntentListAdapter shortcutTypes = new IntentListAdapter(
-                    this, Intent.ACTION_CREATE_SHORTCUT);
-            shortcutChooser.setAdapter(shortcutTypes);
-            shortcutChooser.setLauncher(this);
+        if (mCustomizePagedView != null) {
+            mCustomizePagedView.setLauncher(this);
+            mCustomizePagedView.setDragController(dragController);
+            mCustomizePagedView.update();
         } else {
              ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
              hotseatLeft.setContentDescription(mHotseatLabels[0]);
@@ -2192,6 +2246,9 @@
     }
 
     void showAllApps(boolean animated) {
+        if (mAllAppsGrid.isVisible())
+            return;
+
         if (LauncherApplication.isScreenXLarge()) {
             mWorkspace.shrinkToBottom(animated);
         }
@@ -2199,6 +2256,7 @@
         if (LauncherApplication.isScreenXLarge() && animated) {
             if (isCustomizationDrawerVisible()) {
                 cameraPan(mHomeCustomizationDrawer, (View) mAllAppsGrid);
+                mCustomizePagedView.cleanup();
             } else {
                 cameraZoomOut((View) mAllAppsGrid, true);
             }
@@ -2299,6 +2357,7 @@
                 mWorkspace.unshrink();
             }
             cameraZoomIn(mHomeCustomizationDrawer);
+            mCustomizePagedView.cleanup();
         }
     }
 
@@ -2648,6 +2707,14 @@
     }
 
     /**
+     * A number of packages were updated.
+     */
+    public void bindPackagesUpdated() {
+        // update the customization drawer contents
+        mCustomizePagedView.update();
+    }
+
+    /**
      * Prints out out state for debugging.
      */
     public void dumpState() {
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 6c3ddd2..797bf68 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -104,6 +104,7 @@
         public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
         public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
         public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
+        public void bindPackagesUpdated();
         public boolean isAllAppsVisible();
     }
 
@@ -1320,6 +1321,15 @@
                     }
                 });
             }
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (callbacks == mCallbacks.get()) {
+                        callbacks.bindPackagesUpdated();
+                    }
+                }
+            });
         }
     }
 
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 26805e0..0e8ffa0 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -22,6 +22,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -32,8 +33,14 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Animation.AnimationListener;
 import android.widget.Scroller;
 
+import com.android.launcher.R;
+
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages" (or PagedViewCellLayouts).
@@ -81,16 +88,12 @@
     private ScreenSwitchListener mScreenSwitchListener;
 
     private boolean mDimmedPagesDirty;
+    private final Handler mHandler = new Handler();
 
     public interface ScreenSwitchListener {
         void onScreenSwitch(View newScreen, int newScreenIndex);
     }
 
-    /**
-     * Constructor
-     *
-     * @param context The application's context.
-     */
     public PagedView(Context context) {
         this(context, null);
     }
@@ -158,6 +161,7 @@
 
         mCurrentScreen = Math.max(0, Math.min(currentScreen, getScreenCount() - 1));
         scrollTo(getChildOffset(mCurrentScreen) - getRelativeChildOffset(mCurrentScreen), 0);
+
         invalidate();
         notifyScreenSwitchListener();
     }
@@ -457,6 +461,23 @@
         return mTouchState != TOUCH_STATE_REST;
     }
 
+    protected void animateClickFeedback(View v, final Runnable r) {
+        // animate the view slightly to show click feedback running some logic after it is "pressed"
+        Animation anim = AnimationUtils.loadAnimation(getContext(), 
+                R.anim.paged_view_click_feedback);
+        anim.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {}
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+                r.run();
+            }
+            @Override
+            public void onAnimationEnd(Animation animation) {}
+        });
+        v.startAnimation(anim);
+    }
+
     /*
      * Determines if we should change the touch state to start scrolling after the
      * user moves their touch point too far.
@@ -689,6 +710,10 @@
 
         if (!mScroller.isFinished()) mScroller.abortAnimation();
         mScroller.startScroll(sX, 0, delta, 0, duration);
+
+        // only load some associated pages
+        loadAssociatedPages(mNextScreen);
+
         invalidate();
     }
 
@@ -775,13 +800,86 @@
         };
     }
 
+    private void clearDimmedBitmaps(boolean skipCurrentScreens) {
+        final int count = getChildCount();
+        if (mCurrentScreen < count) {
+            if (skipCurrentScreens) {
+                int lowerScreenBound = Math.max(0, mCurrentScreen - 1);
+                int upperScreenBound = Math.min(mCurrentScreen + 1, count - 1);
+                for (int i = 0; i < count; ++i) {
+                    if (i < lowerScreenBound || i > upperScreenBound) {
+                        PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+                        layout.clearDimmedBitmap();
+                    }
+                }
+            } else {
+                for (int i = 0; i < count; ++i) {
+                    PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+                    layout.clearDimmedBitmap();
+                }
+            }
+        }
+    }
+    Runnable clearLayoutOtherDimmedBitmapsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mScroller.isFinished()) {
+                clearDimmedBitmaps(true);
+                mHandler.removeMessages(0);
+            } else {
+                mHandler.postDelayed(clearLayoutOtherDimmedBitmapsRunnable, 50);
+            }
+        }
+    };
+    Runnable clearLayoutDimmedBitmapsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mScroller.isFinished()) {
+                clearDimmedBitmaps(false);
+                mHandler.removeMessages(0);
+            } else {
+                mHandler.postDelayed(clearLayoutOtherDimmedBitmapsRunnable, 50);
+            }
+        }
+    };
+
+    // called when this paged view is no longer visible
+    public void cleanup() {
+        // clear all the layout dimmed bitmaps
+        mHandler.removeMessages(0);
+        mHandler.postDelayed(clearLayoutDimmedBitmapsRunnable, 500);
+    }
+
+    public void loadAssociatedPages(int screen) {
+        final int count = getChildCount();
+        if (screen < count) {
+            int lowerScreenBound = Math.max(0, screen - 1);
+            int upperScreenBound = Math.min(screen + 1, count - 1);
+            boolean hasDimmedBitmap = false;
+            for (int i = 0; i < count; ++i) {
+                if (lowerScreenBound <= i && i <= upperScreenBound) {
+                    syncPageItems(i);
+                } else {
+                    PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+                    if (layout.getChildCount() > 0) {
+                        layout.removeAllViews();
+                    }
+                    hasDimmedBitmap |= layout.getDimmedBitmapAlpha() > 0.0f;
+                }
+            }
+
+            if (hasDimmedBitmap) {
+                mHandler.removeMessages(0);
+                mHandler.postDelayed(clearLayoutOtherDimmedBitmapsRunnable, 500);
+            }
+        }
+    }
+
     public abstract void syncPages();
     public abstract void syncPageItems(int page);
     public void invalidatePageData() {
         syncPages();
-        for (int i = 0; i < getChildCount(); ++i) {
-            syncPageItems(i);
-        }
+        loadAssociatedPages(mCurrentScreen);
         invalidateDimmedPages();
         requestLayout();
     }
diff --git a/src/com/android/launcher2/PagedViewCellLayout.java b/src/com/android/launcher2/PagedViewCellLayout.java
index 6c9ff6d..16df2a4 100644
--- a/src/com/android/launcher2/PagedViewCellLayout.java
+++ b/src/com/android/launcher2/PagedViewCellLayout.java
@@ -55,6 +55,8 @@
     private final Rect mLayoutRect = new Rect();
     private final Rect mDimmedBitmapRect = new Rect();
 
+    private boolean mCenterContent;
+
     private int mCellCountX;
     private int mCellCountY;
     private int mCellWidth;
@@ -236,13 +238,28 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int count = getChildCount();
 
+        int offsetX = 0;
+        if (mCenterContent) {
+            // determine the max width of all the rows and center accordingly
+            int maxRowWidth = 0;
+            for (int i = 0; i < count; i++) {
+                View child = getChildAt(i);
+                if (child.getVisibility() != GONE) {
+                    PagedViewCellLayout.LayoutParams lp =
+                        (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+                    maxRowWidth = Math.max(maxRowWidth, lp.x + lp.width);
+                }
+            }
+            offsetX = (getMeasuredWidth() / 2) - (maxRowWidth / 2);
+        }
+
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                 PagedViewCellLayout.LayoutParams lp =
                     (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
 
-                int childLeft = lp.x;
+                int childLeft = offsetX + lp.x;
                 int childTop = lp.y;
                 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
             }
@@ -261,6 +278,10 @@
         return super.onTouchEvent(event) || true;
     }
 
+    public void enableCenteredContent(boolean enabled) {
+        mCenterContent = enabled;
+    }
+
     @Override
     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
         final int count = getChildCount();
@@ -327,12 +348,32 @@
         }
     }
 
+    public void clearDimmedBitmap() {
+        setDimmedBitmapAlpha(0.0f);
+        if (mDimmedBitmap != null) {
+            mDimmedBitmap.recycle();
+            mDimmedBitmap = null;
+        }
+    }
+
     private void setChildrenAlpha(float alpha) {
         for (int i = 0; i < getChildCount(); i++) {
             getChildAt(i).setAlpha(alpha);
         }
     }
 
+    public int[] getCellCountForDimensions(int width, int height) {
+        // Always assume we're working with the smallest span to make sure we
+        // reserve enough space in both orientations
+        int smallerSize = Math.min(mCellWidth, mCellHeight);
+
+        // Always round up to next largest cell
+        int spanX = (width + smallerSize) / smallerSize;
+        int spanY = (height + smallerSize) / smallerSize;
+
+        return new int[] { spanX, spanY };
+    }
+
     /**
      * Start dragging the specified child
      *
@@ -343,6 +384,39 @@
         lp.isDragging = true;
     }
 
+    public int estimateCellHSpan(int width) {
+        return (width + mCellWidth) / mCellWidth;
+    }
+    public int estimateCellVSpan(int height) {
+        return (height + mCellHeight) / mCellHeight;
+    }
+    public int[] estimateCellDimensions(int approxWidth, int approxHeight, 
+            int cellHSpan, int cellVSpan) {
+        // NOTE: we are disabling this until we find a good way to approximate this without fully
+        // measuring
+        /*
+        // we may want to use this before any measuring/layout happens, so we pass in an approximate
+        // size for the layout
+        int numWidthGaps = mCellCountX - 1;
+        int numHeightGaps = mCellCountY - 1;
+        int hSpaceLeft = approxWidth - mPaddingLeft
+            - mPaddingRight - (mCellWidth * mCellCountX);
+        int vSpaceLeft = approxHeight - mPaddingTop
+            - mPaddingBottom - (mCellHeight * mCellCountY);
+        int widthGap = hSpaceLeft / numWidthGaps;
+        int heightGap = vSpaceLeft / numHeightGaps;
+        int minGap = Math.min(widthGap, heightGap);
+        return new int[] {
+            (cellHSpan * mCellWidth) + ((cellHSpan - 1) * minGap),
+            (cellVSpan * mCellHeight) + ((cellVSpan - 1) * minGap)
+        };
+        */
+        return new int[] {
+            (cellHSpan * mCellWidth),
+            (cellVSpan * mCellHeight)
+        };
+    }
+
     @Override
     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
@@ -388,6 +462,9 @@
          */
         public boolean isDragging;
 
+        // a data object that you can bind to this layout params
+        private Object mTag;
+
         // X coordinate of the view in the layout.
         @ViewDebug.ExportedProperty
         int x;
@@ -395,6 +472,12 @@
         @ViewDebug.ExportedProperty
         int y;
 
+        public LayoutParams() {
+            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+            cellHSpan = 1;
+            cellVSpan = 1;
+        }
+
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
             cellHSpan = 1;
@@ -440,8 +523,17 @@
             y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
         }
 
+        public Object getTag() {
+            return mTag;
+        }
+
+        public void setTag(Object tag) {
+            mTag = tag;
+        }
+
         public String toString() {
-            return "(" + this.cellX + ", " + this.cellY + ")";
+            return "(" + this.cellX + ", " + this.cellY + ", " +
+                this.cellHSpan + ", " + this.cellVSpan + ")";
         }
     }
 }
diff --git a/src/com/android/launcher2/ShortcutChooser.java b/src/com/android/launcher2/ShortcutChooser.java
deleted file mode 100644
index 1e3e5d0..0000000
--- a/src/com/android/launcher2/ShortcutChooser.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.launcher2;
-
-import com.android.launcher.R;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.AdapterView;
-
-public class ShortcutChooser extends HomeCustomizationItemGallery {
-
-    public ShortcutChooser(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-        // todo: this code sorta overlaps with other places
-        ResolveInfo info = (ResolveInfo)getAdapter().getItem(position);
-        mLauncher.prepareAddItemFromHomeCustomizationDrawer();
-
-        Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        if (info.labelRes == R.string.group_applications) {
-            // Create app shortcuts is a special built-in case of shortcuts
-            createShortcutIntent.putExtra(
-                    Intent.EXTRA_SHORTCUT_NAME,getContext().getString(R.string.group_applications));
-        } else {
-            ComponentName name = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
-            createShortcutIntent.setComponent(name);
-        }
-        mLauncher.processShortcut(createShortcutIntent);
-
-        return true;
-    }
-}
diff --git a/src/com/android/launcher2/WidgetChooser.java b/src/com/android/launcher2/WidgetChooser.java
deleted file mode 100644
index 2218e6d..0000000
--- a/src/com/android/launcher2/WidgetChooser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.TextView;
-
-public class WidgetChooser extends HomeCustomizationItemGallery implements DragSource {
-    private DragController mDragController;
-
-    public WidgetChooser(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public void setDragController(DragController dragger) {
-        mDragController = dragger;
-    }
-
-    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-        Drawable[] drawables = ((TextView)view).getCompoundDrawables();
-        Bitmap bmp = ((BitmapDrawable)drawables[1]).getBitmap();
-        final int w = bmp.getWidth();
-        final int h = bmp.getHeight();
-
-        // We don't really have an accurate location to use.  This will do.
-        int screenX = mMotionDownRawX - (w / 2);
-        int screenY = mMotionDownRawY - h;
-
-        AppWidgetProviderInfo info = (AppWidgetProviderInfo)getAdapter().getItem(position);
-        LauncherAppWidgetInfo dragInfo = new LauncherAppWidgetInfo(info.provider);
-        // TODO: Is this really the best place to do this?
-        dragInfo.minWidth = info.minWidth;
-        dragInfo.minHeight = info.minHeight;
-        mDragController.startDrag(bmp, screenX, screenY,
-                0, 0, w, h, this, dragInfo, DragController.DRAG_ACTION_COPY);
-        mLauncher.hideCustomizationDrawer();
-        return true;
-    }
-
-    public void onDropCompleted(View target, boolean success) {
-    }
-}
-