Add selectable UI to slot view.

Change-Id: Idfbd2f0dfc7475b4f57caff754d2ef89667bd5fd
diff --git a/new3d/src/com/android/gallery3d/ui/AlbumView.java b/new3d/src/com/android/gallery3d/ui/AlbumView.java
index 9032e7d..0575f92 100644
--- a/new3d/src/com/android/gallery3d/ui/AlbumView.java
+++ b/new3d/src/com/android/gallery3d/ui/AlbumView.java
@@ -25,7 +25,7 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.data.MediaSet;
 
-public class AlbumView extends StateView implements SlotView.SlotTapListener {
+public class AlbumView extends TapListenerView {
     public static final String KEY_BUCKET_INDEX = "keyBucketIndex";
     private static final int CHANGE_BACKGROUND = 1;
     private static final int MARGIN_HUD_SLOTVIEW = 5;
@@ -71,6 +71,7 @@
         mBackground = new AdaptiveBackground();
         addComponent(mBackground);
         mSlotView = new SlotView(mContext.getAndroidContext());
+        mSelectionManager = new SelectionManager(mContext.getAndroidContext(), mSlotView);
         addComponent(mSlotView);
         mHud = new HeadUpDisplay(mContext.getAndroidContext());
         addComponent(mHud);
@@ -85,8 +86,8 @@
     private void intializeData(Bundle data) {
         mBucketIndex = data.getInt(KEY_BUCKET_INDEX);
         MediaSet mediaSet = mContext.getDataManager().getSubMediaSet(mBucketIndex);
-        mSlotView.setModel(new GridSlotAdapter(
-                mContext.getAndroidContext(), mediaSet, mSlotView));
+        mSlotView.setModel(new GridSlotAdapter(mContext.getAndroidContext(), mediaSet,
+                mSlotView, mSelectionManager));
     }
 
     public SlotView getSlotView() {
@@ -123,7 +124,8 @@
         }
     }
 
-    public void onSingleTapUp(int slotIndex) {
+    @Override
+    protected void startStateView(int slotIndex) {
         Bundle data = new Bundle();
         data.putInt(PhotoView.KEY_SET_INDEX, mBucketIndex);
         data.putInt(PhotoView.KEY_PHOTO_INDEX, slotIndex);
diff --git a/new3d/src/com/android/gallery3d/ui/GalleryView.java b/new3d/src/com/android/gallery3d/ui/GalleryView.java
index d2e8810..ad731a3 100644
--- a/new3d/src/com/android/gallery3d/ui/GalleryView.java
+++ b/new3d/src/com/android/gallery3d/ui/GalleryView.java
@@ -25,7 +25,7 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.data.MediaSet;
 
-public class GalleryView extends StateView implements SlotView.SlotTapListener {
+public class GalleryView extends TapListenerView {
     private static final int CHANGE_BACKGROUND = 1;
 
     private static final int MARGIN_HUD_SLOTVIEW = 5;
@@ -74,13 +74,14 @@
     private void intializeData() {
         MediaSet mediaSet = mContext.getDataManager().getRootSet();
         mSlotView.setModel(new MediaSetSlotAdapter(
-                mContext.getAndroidContext(), mediaSet, mSlotView));
+                mContext.getAndroidContext(), mediaSet, mSlotView, mSelectionManager));
     }
 
     private void initializeViews() {
         mBackground = new AdaptiveBackground();
         addComponent(mBackground);
         mSlotView = new SlotView(mContext.getAndroidContext());
+        mSelectionManager = new SelectionManager(mContext.getAndroidContext(), mSlotView);
         addComponent(mSlotView);
         mHud = new HeadUpDisplay(mContext.getAndroidContext());
         addComponent(mHud);
@@ -126,7 +127,8 @@
         }
     }
 
-    public void onSingleTapUp(int slotIndex) {
+    @Override
+    public void startStateView(int slotIndex) {
         Bundle data = new Bundle();
         data.putInt(AlbumView.KEY_BUCKET_INDEX, slotIndex);
         mContext.getStateManager().startStateView(AlbumView.class, data);
diff --git a/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java b/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
index 86443a2..deafed7 100644
--- a/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
+++ b/new3d/src/com/android/gallery3d/ui/GridSlotAdapter.java
@@ -22,7 +22,6 @@
 import android.graphics.Rect;
 import android.util.Log;
 
-import com.android.gallery3d.R;
 import com.android.gallery3d.data.FutureListener;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaSet;
@@ -46,17 +45,17 @@
             new HashMap<Integer, MyDisplayItem>(CACHE_CAPACITY);
     private final LinkedHashSet<Integer> mLruSlot =
             new LinkedHashSet<Integer>(CACHE_CAPACITY);
-    private final NinePatchTexture mFrame;
-
     private final MediaSet mMediaSet;
     private final Texture mWaitLoadingTexture;
     private final SlotView mSlotView;
+    private final SelectionManager mSelectionManager;
     private boolean mContentInvalidated = false;
 
-    public GridSlotAdapter(Context context, MediaSet mediaSet, SlotView slotView) {
+    public GridSlotAdapter(Context context, MediaSet mediaSet, SlotView slotView,
+            SelectionManager selectionManager) {
         mSlotView = slotView;
         mMediaSet = mediaSet;
-        mFrame = new NinePatchTexture(context, R.drawable.grid_frame);
+        mSelectionManager = selectionManager;
         ColorTexture gray = new ColorTexture(Color.GRAY);
         gray.setSize(64, 48);
         mWaitLoadingTexture = gray;
@@ -67,7 +66,8 @@
         MyDisplayItem displayItem = mItemMap.get(slotIndex);
         if (displayItem == null || mContentInvalidated) {
             MediaItem item = mMediaSet.getMediaItem(slotIndex);
-            displayItem = new MyDisplayItem(mWaitLoadingTexture, mFrame);
+            displayItem = new MyDisplayItem(mWaitLoadingTexture,
+                    mSelectionManager.getSelectionDrawer());
             mItemMap.put(slotIndex, displayItem);
             item.requestImage(MediaItem.TYPE_MICROTHUMBNAIL,
                     new MyMediaItemListener(slotIndex));
@@ -85,6 +85,9 @@
         // Reclaim the slot
         mLruSlot.remove(slotIndex);
 
+        if (mSelectionManager.isSelectionMode())
+            displayItem.mChecked = mSelectionManager.isSlotSelected(slotIndex);
+
         x += getSlotWidth() / 2;
         y += getSlotHeight() / 2;
         panel.putDisplayItem(displayItem, x, y, 0);
@@ -134,16 +137,17 @@
     private static class MyDisplayItem extends DisplayItem {
 
         private Texture mContent;
-        private final NinePatchTexture mFrame;
+        private final SelectionDrawer mDrawer;
+        private boolean mChecked;
 
-        public MyDisplayItem(Texture content, NinePatchTexture frame) {
-            mFrame = frame;
+        public MyDisplayItem(Texture content, SelectionDrawer drawer) {
+            mDrawer = drawer;
             updateContent(content);
         }
 
         public void updateContent(Texture content) {
             mContent = content;
-            Rect p = mFrame.getPaddings();
+            Rect p = mDrawer.getFramePadding();
 
             int width = mContent.getWidth();
             int height = mContent.getHeight();
@@ -169,13 +173,7 @@
 
         @Override
         public void render(GLCanvas canvas) {
-            int x = -mWidth / 2;
-            int y = -mHeight / 2;
-
-            Rect p = mFrame.getPaddings();
-            mContent.draw(canvas, x + p.left, y + p.top,
-                    mWidth - p.left - p.right, mHeight - p.top - p.bottom);
-            mFrame.draw(canvas, x, y, mWidth, mHeight);
+            mDrawer.draw(canvas, mContent, mWidth, mHeight, mChecked);
         }
     }
 
diff --git a/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java b/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
index cccadf5..76e20ab 100644
--- a/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
+++ b/new3d/src/com/android/gallery3d/ui/MediaSetSlotAdapter.java
@@ -22,7 +22,6 @@
 import android.graphics.Rect;
 import android.util.Log;
 
-import com.android.gallery3d.R;
 import com.android.gallery3d.data.FutureListener;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaSet;
@@ -45,30 +44,27 @@
     private static final int CACHE_CAPACITY = 32;
     private static final int INDEX_NONE = -1;
 
-    private final NinePatchTexture mFrame;
-
-    private final Random mRandom = new Random();
-
     private final MediaSet mRootSet;
     private final Texture mWaitLoadingTexture;
 
-    private final Map<Integer, MyDisplayItem[]> mItemsetMap =
-            new HashMap<Integer, MyDisplayItem[]>(CACHE_CAPACITY);
+    private final Map<Integer, MyDisplayItems> mItemsetMap =
+            new HashMap<Integer, MyDisplayItems>(CACHE_CAPACITY);
     private final LinkedHashSet<Integer> mLruSlot =
             new LinkedHashSet<Integer>(CACHE_CAPACITY);
     private final SlotView mSlotView;
+    private final SelectionManager mSelectionManager;
 
     private boolean mContentInvalidated = false;
     private int mInvalidateIndex = INDEX_NONE;
 
     public MediaSetSlotAdapter(
-            Context context, MediaSet rootSet, SlotView view) {
+            Context context, MediaSet rootSet, SlotView slotView, SelectionManager manager) {
         mRootSet = rootSet;
-        mFrame = new NinePatchTexture(context, R.drawable.stack_frame);
+        mSelectionManager = manager;
         ColorTexture gray = new ColorTexture(Color.GRAY);
         gray.setSize(64, 48);
         mWaitLoadingTexture = gray;
-        mSlotView = view;
+        mSlotView = slotView;
 
         rootSet.setContentListener(new MyContentListener());
     }
@@ -77,7 +73,7 @@
             int slotIndex, int x, int y, DisplayItemPanel panel) {
 
         // Get displayItems from mItemsetMap or create them from MediaSet.
-        MyDisplayItem[] displayItems = mItemsetMap.get(slotIndex);
+        MyDisplayItems displayItems = mItemsetMap.get(slotIndex);
         if (displayItems == null
                 || mContentInvalidated || mInvalidateIndex == slotIndex) {
             displayItems = createDisplayItems(slotIndex);
@@ -87,49 +83,36 @@
         mLruSlot.remove(slotIndex);
 
         // Put displayItems to the panel.
-        Random random = mRandom;
         int left = x + MARGIN_TO_SLOTSIDE;
         int right = x + getSlotWidth() - MARGIN_TO_SLOTSIDE;
         x += getSlotWidth() / 2;
         y += getSlotHeight() / 2;
 
-        // Put the cover items in reverse order, so that the first item is on
-        // top of the rest.
-        for (int i = displayItems.length -1; i > 0; --i) {
-            int dx = random.nextInt(11) - 5;
-            int itemX = (i & 0x01) == 0
-                    ? left + dx + displayItems[i].getWidth() / 2
-                    : right + dx - displayItems[i].getWidth() / 2;
-            int dy = random.nextInt(11) - 10;
-            int theta = random.nextInt(31) - 15;
-            panel.putDisplayItem(displayItems[i], itemX, y + dy, theta);
-        }
-        if (displayItems.length > 0) {
-            panel.putDisplayItem(displayItems[0], x, y, 0);
-        }
+        displayItems.putSlot(panel, x, y, left, right, slotIndex);
+
     }
 
-    private MyDisplayItem[] createDisplayItems(int slotIndex) {
+    private MyDisplayItems createDisplayItems(int slotIndex) {
         MediaSet set = mRootSet.getSubMediaSet(slotIndex);
         set.setContentListener(new SlotContentListener(slotIndex));
 
         MediaItem[] items = set.getCoverMediaItems();
-        MyDisplayItem[] displayItems = new MyDisplayItem[items.length];
-
+        MyDisplayItems displayItems = new MyDisplayItems(mSelectionManager, items.length);
         addSlotToCache(slotIndex, displayItems);
 
         for (int i = 0; i < items.length; ++i) {
-            displayItems[i] = new MyDisplayItem(mWaitLoadingTexture, mFrame);
             items[i].requestImage(MediaItem.TYPE_MICROTHUMBNAIL,
                     new MyMediaItemListener(slotIndex, i));
+            displayItems.setDisplayItem(i, mWaitLoadingTexture);
         }
+
         return displayItems;
     }
 
-    private void addSlotToCache(int slotIndex, MyDisplayItem[] displayItems) {
+    private void addSlotToCache(int slotIndex, MyDisplayItems displayItems) {
         mItemsetMap.put(slotIndex, displayItems);
 
-        // Remove itemsets if the size of mItemsetMap is no less than
+        // Remove an itemset if the size of mItemsetMap is no less than
         // INITIAL_CACHE_CAPACITY and there exists a slot in mLruSlot.
         Iterator<Integer> iter = mLruSlot.iterator();
         for (int i = mItemsetMap.size() - CACHE_CAPACITY;
@@ -163,8 +146,8 @@
 
         public void onFutureDone(Future<? extends Bitmap> future) {
             try {
-                MyDisplayItem[] items = mItemsetMap.get(mSlotIndex);
-                items[mItemIndex].updateContent(new BitmapTexture(future.get()));
+                MyDisplayItems items = mItemsetMap.get(mSlotIndex);
+                items.updateContent(mItemIndex, new BitmapTexture(future.get()));
                 mSlotView.notifyDataInvalidate();
             } catch (Exception e) {
                 Log.v(TAG, "cannot get image", e);
@@ -172,65 +155,105 @@
         }
     }
 
-    private static class MyDisplayItem extends DisplayItem {
+    private static class MyDisplayItems {
+        MyDisplayItem[] mDisplayItems;
+        SelectionManager mSelectionManager;
+        Random mRandom = new Random();
 
-        private Texture mContent;
-        private final NinePatchTexture mFrame;
+        private static class MyDisplayItem extends DisplayItem {
+            private Texture mContent;
+            private boolean mChecked;
+            private boolean mTopItem;
+            private final SelectionDrawer mDrawer;
 
-        public MyDisplayItem(Texture content, NinePatchTexture frame) {
-            mFrame = frame;
-            updateContent(content);
-        }
-
-        public void updateContent(Texture content) {
-            mContent = content;
-            Rect p = mFrame.getPaddings();
-
-            int width = mContent.getWidth();
-            int height = mContent.getHeight();
-
-            float scale = (float) Math.sqrt(EXPECTED_AREA / (width * height));
-            width = (int) (width * scale + 0.5f);
-            height = (int) (height * scale + 0.5f);
-
-            int widthLimit = LENGTH_LIMIT - p.left - p.right;
-            int heightLimit = LENGTH_LIMIT - p.top - p.bottom;
-
-            if (width > widthLimit || height > heightLimit) {
-                if (width * heightLimit > height * widthLimit) {
-                    height = height * widthLimit / width;
-                    width = widthLimit;
-                } else {
-                    width = width * heightLimit / height;
-                    height = heightLimit;
-                }
+            public MyDisplayItem(Texture content, SelectionDrawer drawer) {
+                mDrawer = drawer;
+                updateContent(content);
             }
-            setSize(width + p.left + p.right, height + p.top + p.bottom);
+
+            public void updateContent(Texture content) {
+                mContent = content;
+                Rect p = mDrawer.getFramePadding();
+
+                int width = mContent.getWidth();
+                int height = mContent.getHeight();
+
+                float scale = (float) Math.sqrt(EXPECTED_AREA / (width * height));
+                width = (int) (width * scale + 0.5f);
+                height = (int) (height * scale + 0.5f);
+
+                int widthLimit = LENGTH_LIMIT - p.left - p.right;
+                int heightLimit = LENGTH_LIMIT - p.top - p.bottom;
+
+                if (width > widthLimit || height > heightLimit) {
+                    if (width * heightLimit > height * widthLimit) {
+                        height = height * widthLimit / width;
+                        width = widthLimit;
+                    } else {
+                        width = width * heightLimit / height;
+                        height = heightLimit;
+                    }
+                }
+                setSize(width + p.left + p.right, height + p.top + p.bottom);
+            }
+
+            @Override
+            public void render(GLCanvas canvas) {
+                mDrawer.draw(canvas, mContent, mWidth, mHeight, mChecked, mTopItem);
+            }
         }
 
-        @Override
-        public void render(GLCanvas canvas) {
-            int x = -mWidth / 2;
-            int y = -mHeight / 2;
+        public MyDisplayItems(SelectionManager manager, int size) {
+            mSelectionManager = manager;
+            mDisplayItems = new MyDisplayItem[size];
+        }
 
-            Rect p = mFrame.getPaddings();
-            mContent.draw(canvas, x + p.left, y + p.top,
-                    mWidth - p.left - p.right, mHeight - p.top - p.bottom);
-            mFrame.draw(canvas, x, y, mWidth, mHeight);
+        public void setDisplayItem(int itemIndex, Texture texture) {
+            mDisplayItems[itemIndex] = new MyDisplayItem(texture,
+                    mSelectionManager.getSelectionDrawer());
+        }
+
+        public void updateContent(int itemIndex, Texture texture) {
+            mDisplayItems[itemIndex].updateContent(texture);
+        }
+
+        public void putSlot(DisplayItemPanel panel, int x, int y, int l, int r, int slotIndex) {
+            // Put the cover items in reverse order, so that the first item is on
+            // top of the rest.
+            for (int i = mDisplayItems.length -1; i > 0; --i) {
+                int dx = mRandom.nextInt(11) - 5;
+                int itemX = (i & 0x01) == 0
+                        ? l + dx + mDisplayItems[i].getWidth() / 2
+                        : r + dx - mDisplayItems[i].getWidth() / 2;
+                int dy = mRandom.nextInt(11) - 10;
+                int theta = mRandom.nextInt(31) - 15;
+                panel.putDisplayItem(mDisplayItems[i], itemX, y + dy, theta);
+            }
+            if (mDisplayItems.length > 0) {
+                if (mSelectionManager.isSelectionMode()) {
+                    mDisplayItems[0].mTopItem = true;
+                    mDisplayItems[0].mChecked = mSelectionManager.isSlotSelected(slotIndex);
+                }
+                panel.putDisplayItem(mDisplayItems[0], x, y, 0);
+            }
+        }
+
+        public void removeDisplayItems(DisplayItemPanel panel) {
+            for (MyDisplayItem item : mDisplayItems) {
+                panel.removeDisplayItem(item);
+            }
         }
     }
 
     public void freeSlot(int index, DisplayItemPanel panel) {
-        MyDisplayItem[] displayItems;
+        MyDisplayItems displayItems;
         if (mContentInvalidated) {
             displayItems = mItemsetMap.remove(index);
         } else {
             displayItems = mItemsetMap.get(index);
             mLruSlot.add(index);
         }
-        for (MyDisplayItem item : displayItems) {
-            panel.removeDisplayItem(item);
-        }
+        displayItems.removeDisplayItems(panel);
     }
 
     private void onContentChanged() {
diff --git a/new3d/src/com/android/gallery3d/ui/PhotoView.java b/new3d/src/com/android/gallery3d/ui/PhotoView.java
index e9da521..b99f8a8 100644
--- a/new3d/src/com/android/gallery3d/ui/PhotoView.java
+++ b/new3d/src/com/android/gallery3d/ui/PhotoView.java
@@ -17,8 +17,8 @@
 package com.android.gallery3d.ui;
 
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
 import android.os.Bundle;
 import android.os.Message;
 import android.util.Log;
@@ -93,6 +93,10 @@
 
     }
 
+    public void onLongTap(int slotIndex) {
+        // TODO
+    }
+
     public void setImageViewer(ImageViewer imageViewer) {
         // TODO modify ImageViewer to accepting a data model
         removeComponent(mImageViewer);
diff --git a/new3d/src/com/android/gallery3d/ui/SelectionDrawer.java b/new3d/src/com/android/gallery3d/ui/SelectionDrawer.java
new file mode 100644
index 0000000..15c5b02
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/SelectionDrawer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gallery3d.ui;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import com.android.gallery3d.R;
+
+/**
+ * Drawer class responsible for drawing selectable frame.
+ */
+public class SelectionDrawer {
+    private final NinePatchTexture mFrame;
+    private final ResourceTexture mCheckedItem;
+    private final ResourceTexture mUnCheckedItem;
+    private boolean mSelectionMode;
+    public SelectionDrawer(Context context) {
+        mFrame = new NinePatchTexture(context, R.drawable.grid_frame);
+        mCheckedItem = new ResourceTexture(context, R.drawable.grid_check_on);
+        mUnCheckedItem = new ResourceTexture(context, R.drawable.grid_check_off);
+    }
+
+    public void setSelectionMode(boolean selectionMode) {
+        mSelectionMode = selectionMode;
+    }
+
+    public Rect getFramePadding() {
+        return mFrame.getPaddings();
+    }
+
+    public void draw(GLCanvas canvas, Texture content, int width, int height,
+            boolean checked, boolean drawCheckedBox) {
+        int x = -width / 2;
+        int y = -height / 2;
+        Rect p = mFrame.getPaddings();
+        content.draw(canvas, x + p.left, y + p.top,
+                width - p.left - p.right, height - p.top - p.bottom);
+        mFrame.draw(canvas, x, y, width, height);
+
+        if (drawCheckedBox && mSelectionMode) {
+            int w = mCheckedItem.getWidth() / 2;
+            int h = mCheckedItem.getHeight() / 2;
+            x = width / 2 - w - p.left;
+            y = height / 2 - h - p.left;
+            if (checked)
+                mCheckedItem.draw(canvas, x, y, w, h);
+            else
+                mUnCheckedItem.draw(canvas, x, y, w, h);
+        }
+    }
+
+    public void draw(GLCanvas canvas, Texture content, int width, int height, boolean checked) {
+        draw(canvas, content, width, height, checked, true);
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/SelectionManager.java b/new3d/src/com/android/gallery3d/ui/SelectionManager.java
new file mode 100644
index 0000000..707fb28
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/SelectionManager.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gallery3d.ui;
+
+import android.content.Context;
+import android.os.Vibrator;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ *
+ */
+public class SelectionManager {
+    private Set<Integer> mSelectedSet;
+    private Vibrator mVibrator;
+    private final SelectionDrawer mDrawer;
+
+    public interface SelectionChangeListener {
+        public void onSelectionChange(int index);
+        public void onSelectionModeChange();
+    }
+
+    private final SelectionChangeListener mListener;
+
+    public SelectionManager(Context context, SelectionChangeListener listener) {
+        mListener = listener;
+        mDrawer = new SelectionDrawer(context);
+        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+    }
+
+    public SelectionDrawer getSelectionDrawer() {
+        return mDrawer;
+    }
+
+    public boolean isSelectionMode() {
+        return mSelectedSet != null;
+    }
+
+    public void leaveSelectionMode() {
+        mSelectedSet = null;
+        mDrawer.setSelectionMode(false);
+    }
+
+    public void switchSelectionMode(int slotIndex) {
+        if (mSelectedSet == null) {
+            mSelectedSet = new HashSet<Integer>();
+            mVibrator.vibrate(100);
+            mSelectedSet.add(slotIndex);
+            mDrawer.setSelectionMode(true);
+            mListener.onSelectionModeChange();
+            return;
+        }
+
+        boolean selected = mSelectedSet.contains(slotIndex);
+        if (selected) {
+            mSelectedSet.remove(slotIndex);
+        } else {
+            mSelectedSet = null;
+            leaveSelectionMode();
+        }
+    }
+
+    public boolean isSlotSelected(int slotIndex) {
+        return (mSelectedSet != null) && mSelectedSet.contains(slotIndex);
+    }
+
+    public void selectSlot(int slotIndex) {
+        if (mSelectedSet != null) {
+            if (mSelectedSet.contains(slotIndex)) {
+                mSelectedSet.remove(slotIndex);
+            } else {
+                mSelectedSet.add(slotIndex);
+            }
+            mListener.onSelectionChange(slotIndex);
+        }
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/SlotView.java b/new3d/src/com/android/gallery3d/ui/SlotView.java
index 0fd9669..f8c1786 100644
--- a/new3d/src/com/android/gallery3d/ui/SlotView.java
+++ b/new3d/src/com/android/gallery3d/ui/SlotView.java
@@ -21,9 +21,7 @@
 import android.view.MotionEvent;
 import android.view.animation.DecelerateInterpolator;
 
-public class SlotView extends GLView {
-
-    private static final String TAG = "SlotView";
+public class SlotView extends GLView implements SelectionManager.SelectionChangeListener {
     private static final int MAX_VELOCITY = 2500;
     private static final int NOT_AT_SLOTPOSITION = -1;
 
@@ -133,6 +131,18 @@
         invalidate();
     }
 
+    public void onSelectionChange(int index) {
+        notifySlotInvalidate(index);
+    }
+
+    public void onSelectionModeChange() {
+        for (int i = mVisibleStart; i < mVisibleEnd; i++) {
+            for (int j = 0; j < mRowCount; j++) {
+                notifySlotInvalidate(i * mRowCount + j);
+            }
+        }
+    }
+
     @Override
     protected boolean dispatchTouchEvent(MotionEvent event) {
         // Don't pass the event to the child (MediaDisplayPanel).
@@ -255,6 +265,16 @@
             }
             return true;
         }
+
+        @Override
+        public void onLongPress(MotionEvent e) {
+          float x = e.getX();
+          float y = e.getY();
+          int index = getSlotIndexByPosition(x, y);
+          if (index != SlotView.NOT_AT_SLOTPOSITION) {
+              mSlotTapListener.onLongTap(index);
+          }
+        }
     }
 
     public void setSlotTapListener(SlotTapListener listener) {
@@ -282,6 +302,7 @@
 
     public interface SlotTapListener {
         public void onSingleTapUp(int slotIndex);
+        public void onLongTap(int slotIndex);
     }
 
     public void setGaps(int horizontalGap, int verticalGap) {
diff --git a/new3d/src/com/android/gallery3d/ui/TapListenerView.java b/new3d/src/com/android/gallery3d/ui/TapListenerView.java
new file mode 100644
index 0000000..95242e1
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/TapListenerView.java
@@ -0,0 +1,57 @@
+/*
+ * 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.gallery3d.ui;
+
+
+/**
+ * Selectable StateView.
+ */
+public abstract class TapListenerView extends StateView implements SlotView.SlotTapListener {
+
+    protected SelectionManager mSelectionManager;
+
+    @Override
+    public void onBackPressed() {
+        if (mSelectionManager.isSelectionMode()) {
+            mSelectionManager.leaveSelectionMode();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    protected abstract void startStateView(int slotIndex);
+
+    /* (non-Javadoc)
+     * @see com.android.gallery3d.ui.SlotView.SlotTapListener#onSingleTapUp(int)
+     */
+    @Override
+    public void onSingleTapUp(int slotIndex) {
+        if (!mSelectionManager.isSelectionMode()) {
+            startStateView(slotIndex);
+        } else {
+            mSelectionManager.selectSlot(slotIndex);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see com.android.gallery3d.ui.SlotView.SlotTapListener#onLongTap(int)
+     */
+    @Override
+    public void onLongTap(int slotIndex) {
+        mSelectionManager.switchSelectionMode(slotIndex);
+    }
+}