This CL changes:

1. How menu handler is connected to MediaOperation
2. Use data handler to process background operation
3. Provide a progress dialog to indicate background progress

Change-Id: Id3d4f26c5923e5cf514bdc62ede0c945ec5e1ca0
diff --git a/new3d/src/com/android/gallery3d/app/AlbumPage.java b/new3d/src/com/android/gallery3d/app/AlbumPage.java
index 164fad1..93dc216 100644
--- a/new3d/src/com/android/gallery3d/app/AlbumPage.java
+++ b/new3d/src/com/android/gallery3d/app/AlbumPage.java
@@ -23,16 +23,22 @@
 import android.os.Message;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaOperation;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.ui.AdaptiveBackground;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.GridSlotAdapter;
 import com.android.gallery3d.ui.HeadUpDisplay;
+import com.android.gallery3d.ui.MenuHandler;
+import com.android.gallery3d.ui.MenuItem;
 import com.android.gallery3d.ui.SelectionManager;
 import com.android.gallery3d.ui.SlotView;
 import com.android.gallery3d.ui.SynchronizedHandler;
 
+import java.util.Set;
+
 public class AlbumPage extends ActivityState implements SlotView.SlotTapListener {
+    private static final String TAG = "AlbumPage";
     public static final String KEY_BUCKET_INDEX = "keyBucketIndex";
     private static final int CHANGE_BACKGROUND = 1;
     private static final int MARGIN_HUD_SLOTVIEW = 5;
@@ -46,8 +52,9 @@
     private Bitmap mBgImages[];
     private int mBgIndex = 0;
     private int mBucketIndex;
-
     protected SelectionManager mSelectionManager;
+    private MediaSet mMediaSet;
+    private MenuHandler mMenuHandler;
 
     private GLView mRootPane = new GLView() {
         @Override
@@ -57,8 +64,7 @@
             mHud.layout(0, 0, right - left, bottom - top);
 
             int slotViewTop = mHud.getTopBarBottomPosition() + MARGIN_HUD_SLOTVIEW;
-            int slotViewBottom = mHud.getBottomBarTopPosition()
-                    - MARGIN_HUD_SLOTVIEW;
+            int slotViewBottom = mHud.getBottomBarTopPosition() - MARGIN_HUD_SLOTVIEW;
             mSlotView.layout(0, slotViewTop, right - left, slotViewBottom);
         }
     };
@@ -72,6 +78,7 @@
         }
     }
 
+    @Override
     public void onSingleTapUp(int slotIndex) {
         if (!mSelectionManager.isSelectionMode()) {
             Bundle data = new Bundle();
@@ -85,6 +92,7 @@
         }
     }
 
+    @Override
     public void onLongTap(int slotIndex) {
         mSelectionManager.switchSelectionMode(slotIndex);
         mSlotView.invalidate();
@@ -107,11 +115,14 @@
                 }
             }
         };
+
+        mMenuHandler = new MenuHandler(mContext);
     }
 
     @Override
     public void onPause() {
         mHandler.removeMessages(CHANGE_BACKGROUND);
+        mMenuHandler.onPause();
     }
 
     private void initializeViews() {
@@ -121,7 +132,35 @@
 
         mSelectionManager = new SelectionManager(mContext.getAndroidContext());
         mRootPane.addComponent(mSlotView);
-        mHud = new HeadUpDisplay(mContext.getAndroidContext());
+        mHud = new HeadUpDisplay(mContext.getAndroidContext()) {
+            @Override
+            protected void initializeMenu() {
+                MenuItem share = addBottomMenuItem(R.drawable.icon_share,
+                        R.string.share);
+                MenuItem delete = addBottomMenuItem(R.drawable.icon_delete,
+                        R.string.delete);
+                MenuItem more = addBottomMenuItem(R.drawable.icon_more, R.string.more);
+
+                //TODO: refactor to support dynamic submenu construction.
+                buildShareMenu(share);
+                delete.addMenuItem(R.drawable.icon_delete, R.string.confirm_delete)
+                        .addClickListener(new MenuItem.MenuItemClickListener() {
+                            @Override
+                            public void onClick() {
+                                Set<Integer> set = mSelectionManager.getSelectedSet();
+                                if (set.size() == 0) return;
+
+                                mMenuHandler.handleMediaItemOperation(
+                                        MediaOperation.DELETE, R.string.delete, mMediaSet,
+                                        set, false);
+
+                                mSelectionManager.leaveSelectionMode();
+                            }
+                        });
+                delete.addMenuItem(R.drawable.icon_cancel, R.string.cancel);
+                more.addMenuItem(R.drawable.icon_details, R.string.details);
+            }
+        };
         mRootPane.addComponent(mHud);
         mSlotView.setSlotGaps(HORIZONTAL_GAP_SLOTS, VERTICAL_GAP_SLOTS);
         mSlotView.setSlotTapListener(this);
@@ -133,9 +172,9 @@
 
     private void intializeData(Bundle data) {
         mBucketIndex = data.getInt(KEY_BUCKET_INDEX);
-        MediaSet mediaSet = mContext.getDataManager().getSubMediaSet(mBucketIndex);
+        mMediaSet = mContext.getDataManager().getSubMediaSet(mBucketIndex);
         mSlotView.setListener(new GridSlotAdapter(mContext.getAndroidContext(),
-                mediaSet, mSlotView, mSelectionManager));
+                mMediaSet, mSlotView, mSelectionManager));
     }
 
     private void changeBackground() {
diff --git a/new3d/src/com/android/gallery3d/app/GalleryPage.java b/new3d/src/com/android/gallery3d/app/GalleryPage.java
index 8874a24..97051e0 100644
--- a/new3d/src/com/android/gallery3d/app/GalleryPage.java
+++ b/new3d/src/com/android/gallery3d/app/GalleryPage.java
@@ -23,16 +23,22 @@
 import android.os.Message;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaOperation;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.ui.AdaptiveBackground;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.HeadUpDisplay;
 import com.android.gallery3d.ui.MediaSetSlotAdapter;
+import com.android.gallery3d.ui.MenuHandler;
+import com.android.gallery3d.ui.MenuItem;
 import com.android.gallery3d.ui.SelectionManager;
 import com.android.gallery3d.ui.SlotView;
 import com.android.gallery3d.ui.SynchronizedHandler;
 
+import java.util.Set;
+
 public class GalleryPage extends ActivityState implements SlotView.SlotTapListener {
+    private static final String TAG = "GalleryPage";
     private static final int CHANGE_BACKGROUND = 1;
 
     private static final int MARGIN_HUD_SLOTVIEW = 5;
@@ -46,6 +52,8 @@
     private int mBgIndex = 0;
 
     protected SelectionManager mSelectionManager;
+    private MediaSet mMediaSet;
+    private MenuHandler mMenuHandler;
 
     private GLView mRootPane = new GLView() {
         @Override
@@ -55,8 +63,7 @@
             mHud.layout(0, 0, right - left, bottom - top);
 
             int slotViewTop = mHud.getTopBarBottomPosition() + MARGIN_HUD_SLOTVIEW;
-            int slotViewBottom = mHud.getBottomBarTopPosition()
-                    - MARGIN_HUD_SLOTVIEW;
+            int slotViewBottom = mHud.getBottomBarTopPosition() - MARGIN_HUD_SLOTVIEW;
 
             mSlotView.layout(0, slotViewTop, right - left, slotViewBottom);
         }
@@ -71,6 +78,7 @@
         }
     }
 
+    @Override
     public void onSingleTapUp(int slotIndex) {
         if (!mSelectionManager.isSelectionMode()) {
             Bundle data = new Bundle();
@@ -82,6 +90,7 @@
         }
     }
 
+    @Override
     public void onLongTap(int slotIndex) {
         mSelectionManager.switchSelectionMode(slotIndex);
         mSlotView.invalidate();
@@ -105,11 +114,14 @@
                 }
             }
         };
+
+        mMenuHandler = new MenuHandler(mContext);
     }
 
     @Override
     public void onPause() {
         mHandler.removeMessages(CHANGE_BACKGROUND);
+        mMenuHandler.onPause();
     }
 
     @Override
@@ -119,9 +131,9 @@
     }
 
     private void intializeData() {
-        MediaSet mediaSet = mContext.getDataManager().getRootSet();
+        mMediaSet = mContext.getDataManager().getRootSet();
         mSlotView.setListener(new MediaSetSlotAdapter(
-                mContext.getAndroidContext(), mediaSet, mSlotView, mSelectionManager));
+                mContext.getAndroidContext(), mMediaSet, mSlotView, mSelectionManager));
     }
 
     private void initializeViews() {
@@ -133,7 +145,34 @@
         mSlotView.setSlotTapListener(this);
 
         mRootPane.addComponent(mSlotView);
-        mHud = new HeadUpDisplay(mContext.getAndroidContext());
+        mHud = new HeadUpDisplay(mContext.getAndroidContext()) {
+            @Override
+            protected void initializeMenu() {
+                MenuItem share = addBottomMenuItem(R.drawable.icon_share,
+                        R.string.share);
+                MenuItem delete = addBottomMenuItem(R.drawable.icon_delete,
+                        R.string.delete);
+                MenuItem more = addBottomMenuItem(R.drawable.icon_more, R.string.more);
+
+                buildShareMenu(share);
+                delete.addMenuItem(R.drawable.icon_delete, R.string.confirm_delete)
+                        .addClickListener(new MenuItem.MenuItemClickListener() {
+                            @Override
+                            public void onClick() {
+                                Set<Integer> set = mSelectionManager.getSelectedSet();
+                                if (set.size() == 0) return;
+
+                                mMenuHandler.handleMediaItemOperation(
+                                        MediaOperation.DELETE, R.string.delete, mMediaSet, 
+                                        set, true);
+                                mSelectionManager.leaveSelectionMode();
+                            }
+                        });
+
+                delete.addMenuItem(R.drawable.icon_cancel, R.string.cancel);
+                more.addMenuItem(R.drawable.icon_details, R.string.details);
+            }
+        };
         mRootPane.addComponent(mHud);
 
         loadBackgroundBitmap(R.drawable.square,
diff --git a/new3d/src/com/android/gallery3d/data/ComboAlbumSet.java b/new3d/src/com/android/gallery3d/data/ComboAlbumSet.java
index 5918a69..11da912 100644
--- a/new3d/src/com/android/gallery3d/data/ComboAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/ComboAlbumSet.java
@@ -36,6 +36,7 @@
         return mUniqueId;
     }
 
+    @Override
     public MediaSet getSubMediaSet(int index) {
         for (MediaSet set : mSets) {
             int size = set.getSubMediaSetCount();
@@ -47,6 +48,7 @@
         throw new IndexOutOfBoundsException();
     }
 
+    @Override
     public int getSubMediaSetCount() {
         int count = 0;
         for (MediaSet set : mSets) {
@@ -59,6 +61,7 @@
         return TAG;
     }
 
+    @Override
     public int getTotalMediaItemCount() {
         int count = 0;
         for (MediaSet set : mSets) {
@@ -78,4 +81,18 @@
             mListener.onContentChanged();
         }
     }
+
+    @Override
+    public int getSupportedOperations() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        return false;
+    }
+
+    @Override
+    public void delete() {
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/LocalAlbum.java b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
index 4e56750..5319388 100644
--- a/new3d/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
@@ -158,4 +158,18 @@
     public void reload() {
         // do nothing
     }
+
+    @Override
+    public int getSupportedOperations() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        return false;
+    }
+
+    @Override
+    public void delete() {
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java b/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
index e18cd4c..84a3473 100644
--- a/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
@@ -19,8 +19,8 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Video.VideoColumns;
 
 import com.android.gallery3d.app.GalleryContext;
@@ -72,10 +72,12 @@
         return mUniqueId;
     }
 
+    @Override
     public synchronized MediaSet getSubMediaSet(int index) {
         return mAlbums.get(index);
     }
 
+    @Override
     public synchronized int getSubMediaSetCount() {
         return mAlbums.size();
     }
@@ -84,6 +86,7 @@
         return TAG;
     }
 
+    @Override
     public int getTotalMediaItemCount() {
         int total = 0;
         for (MediaSet album : mAlbums) {
@@ -125,4 +128,22 @@
 
         Collections.sort(mAlbums, LocalAlbum.sBucketNameComparator);
     }
+
+    @Override
+    public int getSupportedOperations() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public void delete() {
+        // TODO Auto-generated method stub
+
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/LocalImage.java b/new3d/src/com/android/gallery3d/data/LocalImage.java
index 6aad1dc..034f769 100644
--- a/new3d/src/com/android/gallery3d/data/LocalImage.java
+++ b/new3d/src/com/android/gallery3d/data/LocalImage.java
@@ -16,8 +16,6 @@
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.util.Utils;
-
 import android.content.ContentResolver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
@@ -26,6 +24,8 @@
 import android.provider.MediaStore.Images.ImageColumns;
 import android.util.Log;
 
+import com.android.gallery3d.util.Utils;
+
 import java.io.BufferedInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -39,6 +39,7 @@
     private static final int FULLIMAGE_TARGET_SIZE = 2048;
     private static final int FULLIMAGE_MAX_NUM_PIXELS = 5 * 1024 * 1024;
     private static final String TAG = "LocalImage";
+    private static final int SUPPORTED_OPERATIONS = MediaOperation.DELETE;
 
     // Must preserve order between these indices and the order of the terms in
     // the following PROJECTION array.
@@ -170,4 +171,25 @@
 
         return item;
     }
+
+    @Override
+    public int getSupportedOperations() {
+        return SUPPORTED_OPERATIONS;
+    }
+
+    @Override
+    public boolean supportOperation(int operation) {
+        return (operation & SUPPORTED_OPERATIONS) != 0;
+    }
+
+    @Override
+    public void delete() {
+        if (!supportOperation(MediaOperation.DELETE)) return;
+
+        Log.v(TAG, "Delete LocalImage: " + mCaption);
+        try {
+            Thread.currentThread().sleep(1000);
+        } catch (InterruptedException e) {
+        }
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/LocalVideo.java b/new3d/src/com/android/gallery3d/data/LocalVideo.java
index 21328ff..01c526a 100644
--- a/new3d/src/com/android/gallery3d/data/LocalVideo.java
+++ b/new3d/src/com/android/gallery3d/data/LocalVideo.java
@@ -16,18 +16,21 @@
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.util.Utils;
-
 import android.content.ContentResolver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Video.VideoColumns;
+import android.util.Log;
+
+import com.android.gallery3d.util.Utils;
 
 // LocalVideo represents a video in the local storage.
 public class LocalVideo extends LocalMediaItem {
+    private static final String TAG = "LocalVideo";
 
     private static final int MICRO_TARGET_PIXELS = 128 * 128;
+    private static final int SUPPORTED_OPERATIONS = MediaOperation.DELETE;
 
     // Must preserve order between these indices and the order of the terms in
     // the following PROJECTION array.
@@ -114,4 +117,25 @@
 
         return item;
     }
+
+    @Override
+    public int getSupportedOperations() {
+        return SUPPORTED_OPERATIONS;
+    }
+
+    @Override
+    public boolean supportOperation(int operation) {
+        return (operation & SUPPORTED_OPERATIONS) != 0;
+    }
+
+    @Override
+    public void delete() {
+        if (!supportOperation(MediaOperation.DELETE)) return;
+
+        Log.v(TAG, "Delete LocalVideo: " + mCaption);
+        try {
+            Thread.currentThread().sleep(1000);
+        } catch (InterruptedException e) {
+        }
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/MediaItem.java b/new3d/src/com/android/gallery3d/data/MediaItem.java
index c7805b8..d3c33f7 100644
--- a/new3d/src/com/android/gallery3d/data/MediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/MediaItem.java
@@ -33,6 +33,10 @@
     public static final int IMAGE_ERROR = -1;
 
     public abstract long getUniqueId();
+    public abstract int getSupportedOperations();
+    public abstract boolean supportOperation(int operation);
+    public abstract void delete();
+
     public abstract Future<Bitmap>
             requestImage(int type, FutureListener<? super Bitmap> listener);
 }
diff --git a/new3d/src/com/android/gallery3d/data/MediaOperation.java b/new3d/src/com/android/gallery3d/data/MediaOperation.java
new file mode 100644
index 0000000..24b7354
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/data/MediaOperation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.data;
+
+import java.util.Map;
+
+public interface MediaOperation {
+    public static final int DELETE              = 0x001;
+    public static final int ROTATE              = 0x002;
+    public static final int CROP                = 0x008;
+    public static final int GET_META_DATA       = 0x010;
+    public static final int GET_MIME_TYPE       = 0x020;
+    public static final int SET_AS              = 0x040;
+}
diff --git a/new3d/src/com/android/gallery3d/data/MediaSet.java b/new3d/src/com/android/gallery3d/data/MediaSet.java
index b51cf27..2daa543 100644
--- a/new3d/src/com/android/gallery3d/data/MediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/MediaSet.java
@@ -91,4 +91,8 @@
     }
 
     public abstract void reload();
+
+    public abstract int getSupportedOperations();
+    public abstract boolean supportOpeation(int operation);
+    public abstract void delete();
 }
diff --git a/new3d/src/com/android/gallery3d/data/MergeAlbum.java b/new3d/src/com/android/gallery3d/data/MergeAlbum.java
index b3680d2..1754583 100644
--- a/new3d/src/com/android/gallery3d/data/MergeAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/MergeAlbum.java
@@ -142,6 +142,21 @@
             mListener.onContentChanged();
         }
     }
+
+    @Override
+    public void delete() {
+
+    }
+
+    @Override
+    public int getSupportedOperations() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        return false;
+    }
 }
 
 class FetchCache {
diff --git a/new3d/src/com/android/gallery3d/data/MergeAlbumSet.java b/new3d/src/com/android/gallery3d/data/MergeAlbumSet.java
index f4f20bd..4e8680c 100644
--- a/new3d/src/com/android/gallery3d/data/MergeAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/MergeAlbumSet.java
@@ -120,4 +120,18 @@
             mListener.onContentChanged();
         }
     }
+
+    @Override
+    public void delete() {
+    }
+
+    @Override
+    public int getSupportedOperations() {
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        return false;
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/PicasaAlbum.java b/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
index f82c923..f042c82 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
@@ -107,4 +107,22 @@
     public void reload() {
         // do nothing
     }
+
+    @Override
+    public int getSupportedOperations() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public void delete() {
+        // TODO Auto-generated method stub
+
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java b/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
index d7c839e..691ff70 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
@@ -41,6 +41,7 @@
         return mAlbums.get(index);
     }
 
+    @Override
     public int getSubMediaSetCount() {
         return mAlbums.size();
     }
@@ -53,6 +54,7 @@
         return DataManager.makeId(DataManager.ID_PICASA_ALBUM_SET, 0);
     }
 
+    @Override
     public int getTotalMediaItemCount() {
         int totalCount = 0;
         for (PicasaAlbum album : mAlbums) {
@@ -83,4 +85,22 @@
         mAlbums.addAll(mLoadBuffer);
         mLoadBuffer.clear();
     }
+
+    @Override
+    public int getSupportedOperations() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public boolean supportOpeation(int operation) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public void delete() {
+        // TODO Auto-generated method stub
+
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/PicasaImage.java b/new3d/src/com/android/gallery3d/data/PicasaImage.java
index 92d599b..929fb8b 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaImage.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaImage.java
@@ -36,6 +36,7 @@
 // PicasaImage is an image in the Picasa account.
 public class PicasaImage extends MediaItem {
     private static final String TAG = "PicasaImage";
+    private static final int SUPPORTED_OPERATIONS = 0;
 
     private final PicasaTask[] mTasks = new PicasaTask[MediaItem.TYPE_COUNT];
     private final GalleryContext mContext;
@@ -178,4 +179,25 @@
             return null;
         }
     }
+
+    @Override
+    public int getSupportedOperations() {
+        return SUPPORTED_OPERATIONS;
+    }
+
+    @Override
+    public boolean supportOperation(int operation) {
+        return (operation & SUPPORTED_OPERATIONS) != 0;
+    }
+
+    @Override
+    public void delete() {
+        if (!supportOperation(MediaOperation.DELETE)) return;
+
+        Log.v(TAG, "Delete PicasaImage: ");
+        try {
+            Thread.currentThread().sleep(1000);
+        } catch (InterruptedException e) {
+        }
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java b/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
index 8e84797..e6e67c0 100644
--- a/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
+++ b/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
@@ -28,11 +28,8 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.util.Utils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class HeadUpDisplay extends GLView {
-
+public abstract class HeadUpDisplay extends GLView {
+    private static final String TAG = "HeadUpDisplay";
     private static final int POPUP_WINDOW_OVERLAP = 20;
     private static final int POPUP_TRIANGLE_OFFSET = 15;
 
@@ -52,8 +49,6 @@
     private GLListView mListView;
 
     private GLView mAnchorView;
-    private final HashMap<MenuItem, MenuAdapter> mContentMap =
-            new HashMap<MenuItem, MenuAdapter>();
 
     private ResourceTexture mPathIcons[];
     private String mPathTitle[];
@@ -109,7 +104,7 @@
         }
     }
 
-    private MenuItem addBottomMenuItem(int iconId, int stringId) {
+    protected MenuItem addBottomMenuItem(int iconId, int stringId) {
         MenuItem item = new MenuItem(mContext, iconId, stringId);
         item.setHighlight(mHighlight);
         mBottomBar.addComponent(item);
@@ -123,6 +118,8 @@
         mTopBar.addComponent(button);
     }
 
+    abstract protected void initializeMenu();
+
     private void initialize() {
         Context context = mContext;
 
@@ -139,20 +136,7 @@
                 context, IconLabel.NULL_ID, R.string.items));
         addTopMenuButton(R.string.deselect_all);
 
-        MenuItem share = addBottomMenuItem(R.drawable.icon_share, R.string.share);
-        MenuItem delete = addBottomMenuItem(R.drawable.icon_delete, R.string.delete);
-        MenuItem more = addBottomMenuItem(R.drawable.icon_more, R.string.more);
-
-        MenuAdapter deleteMenu = new MenuAdapter(context);
-        deleteMenu.addMenu(R.drawable.icon_delete, R.string.confirm_delete);
-        deleteMenu.addMenu(R.drawable.icon_cancel, R.string.cancel);
-
-        MenuAdapter moreMenu = new MenuAdapter(context);
-        moreMenu.addMenu(R.drawable.icon_details, R.string.details);
-
-        mContentMap.put(share, buildShareMenu(context));
-        mContentMap.put(delete, deleteMenu);
-        mContentMap.put(more, moreMenu);
+        initializeMenu();
 
         mPathbar.push(mPathIcons[0], mPathTitle[0]);
         mPathbar.push(mPathIcons[1], mPathTitle[1]);
@@ -212,6 +196,7 @@
     private void initializePopupWindow() {
         Context context = mContext;
         mListView = new GLListView(context);
+        mListView.setOnItemSelectedListener(new MyMenuItemListener());
         mPopupWindow = new PopupWindow();
 
         mPopupWindow.setBackground(
@@ -226,29 +211,37 @@
         super.addComponent(mPopupWindow);
     }
 
-    private MenuAdapter buildShareMenu(Context context) {
+    protected void buildShareMenu(MenuItem menu) {
         Intent intent = new Intent(Intent.ACTION_SEND);
         // TODO: the type should match to the selected items
         intent.setType("image/jpeg");
-        MenuAdapter menu = new MenuAdapter(context);
         PackageManager packageManager = mContext.getPackageManager();
         for(ResolveInfo info
                 : packageManager.queryIntentActivities(intent, 0)) {
             String label = info.loadLabel(packageManager).toString();
             Drawable icon = info.loadIcon(packageManager);
-            menu.addMenu(icon, label);
+            menu.addMenuItem(icon, label);
         }
-        return menu;
     }
 
-    private class MySelectedListener implements OnSelectedListener {
+    private class MyMenuItemListener implements GLListView.OnItemSelectedListener {
+        @Override
+        public void onItemSelected(GLView view, int position) {
+            MenuItem item = (MenuItem) view;
+            item.notifyClickListeners();
+            mPopupWindow.popoff();
+        }
+    }
 
-        public void onSelected(GLView source) {
+    private class MySelectedListener implements MenuItemBar.OnSelectedListener {
+
+        @Override
+        public void onSelected(MenuItem source) {
             if (source == null) {
                 mPopupWindow.popoff();
             } else {
                 if (mPopupWindow == null) initializePopupWindow();
-                mListView.setDataModel(mContentMap.get(source));
+                mListView.setDataModel(source.getSubMenu());
                 layoutPopupWindow(source);
                 if (mPopupWindow.getVisibility() != GLView.VISIBLE) {
                     mPopupWindow.popup();
@@ -259,6 +252,7 @@
 
     private class MyPathbarListener implements Pathbar.OnClickedListener {
 
+        @Override
         public void onClicked(Pathbar source, int index) {
             int size = mPathbar.size();
             if (index < size - 1) {
@@ -274,48 +268,6 @@
         }
     }
 
-    private static class MenuAdapter implements GLListView.Model {
-
-        private final ArrayList<IconLabel> mContent = new ArrayList<IconLabel>();
-        private final Context mContext;
-
-        public MenuAdapter(Context context) {
-            mContext = context;
-        }
-
-        public void addMenu(int icon, int title) {
-            mContent.add(new IconLabel(mContext, icon, title));
-        }
-
-        public void addMenu(Drawable icon, String title) {
-
-            DrawableTexture iconTexture = null;
-            if (icon != null) {
-                iconTexture = new DrawableTexture(icon);
-                float target = 45;
-                int width = icon.getIntrinsicWidth();
-                int height = icon.getIntrinsicHeight();
-                float scale = target / Math.max(width, height);
-                iconTexture.setSize((int) (width * scale + .5f),
-                        (int) (height * scale + .5f));
-            }
-
-            mContent.add(new IconLabel(mContext, iconTexture, title));
-        }
-
-        public GLView getView(int index) {
-            return mContent.get(index);
-        }
-
-        public boolean isSelectable(int index) {
-            return true;
-        }
-
-        public int size() {
-            return mContent.size();
-        }
-    }
-
     public int getTopBarBottomPosition() {
         Rect rect = new Rect();
         getBoundsOf(mTopBar, rect);
diff --git a/new3d/src/com/android/gallery3d/ui/MenuHandler.java b/new3d/src/com/android/gallery3d/ui/MenuHandler.java
new file mode 100644
index 0000000..97849d3
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/MenuHandler.java
@@ -0,0 +1,190 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryContext;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaOperation;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.util.ComboFuture;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.FutureTask;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+public class MenuHandler extends Handler {
+    private static final String TAG = "MenuHandler";
+    private ProgressDialog mDialog;
+    private MenuTask mCurrentTask;
+    private Handler mMainHandler;
+
+    public MenuHandler(GalleryContext context) {
+        super(context.getDataManager().getDataLooper());
+        mDialog = new ProgressDialog(context.getAndroidContext()) {
+            public void onBackPressed() {
+                mCurrentTask.requestCancel();
+            }
+        };
+        mMainHandler = new Handler(context.getMainLooper());
+        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+    }
+
+    private class MenuTask extends ComboFuture<Void> {
+        private class CallableMediaOperation implements Callable<Void> {
+            MediaItem mItem;
+            int mOperation;
+
+            public CallableMediaOperation(MediaItem item, int operation) {
+                mItem = item;
+                mOperation = operation;
+            }
+
+            public Void call() throws Exception {
+                switch (mOperation) {
+                    case MediaOperation.DELETE:
+                        mItem.delete();
+                        break;
+                }
+                return null;
+            }
+        }
+
+        ArrayList<MediaItem> mItems;
+        int mOperation;
+        int mIndex;
+        boolean mCancelled;
+
+        public MenuTask(int operation, final int title, MediaSet parent, Set<Integer> selectedSet,
+                boolean isSubSet, FutureListener<Void> listener) {
+            super(listener);
+            mOperation = operation;
+
+            // calculate how many items to operate
+            int total = 0;
+            if (isSubSet) {
+                for (int index : selectedSet) {
+                    MediaSet subset = parent.getSubMediaSet(index);
+                    total += subset.getMediaItemCount();
+                }
+            } else {
+                total = selectedSet.size();
+            }
+
+            // add media item to process queue
+            mItems = new ArrayList<MediaItem>(total);
+            mIndex = 0;
+            for (int index : selectedSet) {
+                if (isSubSet) {
+                    MediaSet subset = parent.getSubMediaSet(index);
+                    int setCount = subset.getMediaItemCount();
+                    for (int i = 0; i < setCount; i++) {
+                        mItems.add(subset.getMediaItem(i));
+                    }
+                } else {
+                    mItems.add(parent.getMediaItem(index));
+                }
+            }
+
+            final int max = total;
+            mMainHandler.post(new Runnable() {
+                public void run() {
+                    mDialog.setTitle(title);
+                    mDialog.setMax(max);
+                    mDialog.show();
+                    mDialog.setProgress(0);
+                }
+            });
+
+//          start the first operation
+            execute();
+        }
+
+        @Override
+        protected Future<?> executeNextTask(int index, Future<?> current) throws Exception {
+            if (mCancelled || index >= mItems.size()) return null;
+            FutureTask task = new FutureTask<Void>(new CallableMediaOperation(mItems.get(index),
+                    mOperation), this);
+            post(task);
+            return task;
+        }
+
+        public synchronized void onFutureDone(Future<?> currentTask) {
+            mMainHandler.post(new Runnable() {
+                public void run() {
+                    mDialog.setProgress(++mIndex);
+                }
+            });
+            // move on to next item
+            super.onFutureDone(currentTask);
+        }
+
+        @Override
+        protected synchronized void onCancel() {
+            super.onCancel();
+            mCancelled = true;
+            mMainHandler.post(new Runnable() {
+                public void run() {
+                    mDialog.dismiss();
+                }
+            });
+        }
+    }
+
+    public void handleMediaItemOperation(int operation, int title, MediaSet parent,
+            Set<Integer> selectedSet, boolean isSubSet) {
+        mCurrentTask = new MenuTask(operation, title, parent, selectedSet, isSubSet,
+                new FutureListener<Void>() {
+                    public void onFutureDone(Future<? extends Void> future) {
+                        mMainHandler.post(new Runnable() {
+                            public void run() {
+                                mDialog.dismiss();
+                            }
+                        });
+                    }
+                });
+    }
+
+    public void onPause() {
+        if (mCurrentTask != null) {
+            mCurrentTask.requestCancel();
+            try {
+                mCurrentTask.get();
+            } catch (CancellationException ce) {
+                // ignore it.
+            } catch (ExecutionException ee) {
+                // ignore it.
+            } catch (InterruptedException ie) {
+                // ignore it.
+            }
+            mCurrentTask = null;
+        }
+        mDialog.dismiss();
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/MenuItem.java b/new3d/src/com/android/gallery3d/ui/MenuItem.java
index 0df9b97..40aced7 100644
--- a/new3d/src/com/android/gallery3d/ui/MenuItem.java
+++ b/new3d/src/com/android/gallery3d/ui/MenuItem.java
@@ -17,14 +17,105 @@
 package com.android.gallery3d.ui;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaOperation;
 
 public class MenuItem extends IconLabel {
+    private static final String TAG = "MenuItem";
     private boolean mSelected;
     private Texture mHighlight;
+    protected Context mContext;
+    private SubMenu mSubMenu;
+    private final ArrayList<MenuItemClickListener> mListeners = new ArrayList<MenuItemClickListener>();
+
+    private static class SubMenu implements GLListView.Model {
+        ArrayList<MenuItem> mItems;
+
+        public MenuItem addMenuItem(MenuItem item) {
+            if (mItems == null) {
+                mItems = new ArrayList<MenuItem>();
+            }
+            mItems.add(item);
+            return item;
+        }
+
+        @Override
+        public GLView getView(int index) {
+            if (mItems == null) return null;
+            return mItems.get(index);
+        }
+
+        @Override
+        public boolean isSelectable(int index) {
+            return true;
+        }
+
+        @Override
+        public int size() {
+            if (mItems == null) return 0;
+            return mItems.size();
+        }
+    }
+
+    public interface MenuItemClickListener {
+        public void onClick();
+    }
 
     public MenuItem(Context context, int icon, int label) {
         super(context, icon, label);
+        mContext = context;
+        mSubMenu = new SubMenu();
+    }
+
+    public MenuItem(Context context, BasicTexture texture, String label) {
+        super(context, texture, label);
+    }
+
+    public MenuItem addMenuItem(MenuItem item) {
+        return mSubMenu.addMenuItem(item);
+    }
+
+    public MenuItem addMenuItem(int icon, int title) {
+        return mSubMenu.addMenuItem(new MenuItem(mContext, icon, title));
+    }
+
+    public MenuItem addMenuItem(Drawable icon, String title) {
+        DrawableTexture iconTexture = null;
+        if (icon != null) {
+            iconTexture = new DrawableTexture(icon);
+            float target = 45;
+            int width = icon.getIntrinsicWidth();
+            int height = icon.getIntrinsicHeight();
+            float scale = target / Math.max(width, height);
+            iconTexture.setSize((int) (width * scale + .5f),
+                    (int) (height * scale + .5f));
+        }
+
+        return mSubMenu.addMenuItem(new MenuItem(mContext, iconTexture, title));
+    }
+
+    public GLListView.Model getSubMenu() {
+        return mSubMenu;
+    }
+
+    public void addClickListener(MenuItemClickListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeClickListener(MenuItemClickListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public void notifyClickListeners() {
+        for (MenuItemClickListener listener : mListeners) {
+            listener.onClick();
+        }
     }
 
     public void setHighlight(Texture texture) {
@@ -32,7 +123,8 @@
     }
 
     protected void setSelected(boolean selected) {
-        if (selected == mSelected) return;
+        if (selected == mSelected)
+            return;
         mSelected = selected;
         invalidate();
     }
@@ -44,8 +136,8 @@
             int height = getHeight();
             if (mHighlight instanceof NinePatchTexture) {
                 Rect p = ((NinePatchTexture) mHighlight).getPaddings();
-                mHighlight.draw(canvas, -p.left, -p.top,
-                        width + p.left + p.right, height + p.top + p.bottom);
+                mHighlight.draw(canvas, -p.left, -p.top, width + p.left
+                        + p.right, height + p.top + p.bottom);
             } else {
                 mHighlight.draw(canvas, 0, 0, width, height);
             }
diff --git a/new3d/src/com/android/gallery3d/ui/MenuItemBar.java b/new3d/src/com/android/gallery3d/ui/MenuItemBar.java
index 7b7dbb0..f792b1d 100644
--- a/new3d/src/com/android/gallery3d/ui/MenuItemBar.java
+++ b/new3d/src/com/android/gallery3d/ui/MenuItemBar.java
@@ -21,6 +21,10 @@
 public class MenuItemBar extends MenuBar {
     public static final int INDEX_NONE = -1;
 
+    public interface OnSelectedListener {
+        public void onSelected(MenuItem source);
+    }
+
     private OnSelectedListener mOnSelectedListener;
     private MenuItem mSelectedItem;
     private boolean mSelectionChanged = false;
diff --git a/new3d/src/com/android/gallery3d/ui/SelectionManager.java b/new3d/src/com/android/gallery3d/ui/SelectionManager.java
index 766738f..b61b09d 100644
--- a/new3d/src/com/android/gallery3d/ui/SelectionManager.java
+++ b/new3d/src/com/android/gallery3d/ui/SelectionManager.java
@@ -19,13 +19,17 @@
 import android.content.Context;
 import android.os.Vibrator;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 
+import com.android.gallery3d.data.MediaOperation;
+
 public class SelectionManager {
     private Set<Integer> mSelectedSet;
-    private Vibrator mVibrator;
+    private final Vibrator mVibrator;
     private final SelectionDrawer mDrawer;
+    private ArrayList<MediaOperation> mSelectedMedia;
 
     public SelectionManager(Context context) {
         mDrawer = new SelectionDrawer(context);
@@ -76,4 +80,8 @@
             }
         }
     }
+
+    public Set<Integer> getSelectedSet() {
+        return mSelectedSet;
+    }
 }