Menu handler for details, delete and rotate (cw/ccw)

Change-Id: I714f9a4535b7861cf97f4f6275368e8d13acb67e
diff --git a/new3d/src/com/android/gallery3d/app/AlbumPage.java b/new3d/src/com/android/gallery3d/app/AlbumPage.java
index 6a77b72..7285cdc 100644
--- a/new3d/src/com/android/gallery3d/app/AlbumPage.java
+++ b/new3d/src/com/android/gallery3d/app/AlbumPage.java
@@ -42,6 +42,7 @@
 
     private AdaptiveBackground mBackground;
     private AlbumView mAlbumView;
+    private HudMenu mHudMenu;
     private HeadUpDisplay mHud;
     private SynchronizedHandler mHandler;
     private Bitmap mBgImages[];
@@ -116,6 +117,7 @@
     @Override
     public void onPause() {
         mHandler.removeMessages(CHANGE_BACKGROUND);
+        mHudMenu.onPause();
     }
 
     private void initializeViews() {
@@ -126,7 +128,8 @@
         mAlbumView = new AlbumView(mContext, mSelectionManager);
         mRootPane.addComponent(mAlbumView);
         mHud = new HeadUpDisplay(mContext.getAndroidContext());
-        mHud.setMenu(new HudMenu(mContext, mSelectionManager));
+        mHudMenu = new HudMenu(mContext, mSelectionManager);
+        mHud.setMenu(mHudMenu);
         mRootPane.addComponent(mHud);
         mAlbumView.setSlotTapListener(this);
 
diff --git a/new3d/src/com/android/gallery3d/app/AlbumSetPage.java b/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
index 0563400..a1ce4ef 100644
--- a/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -41,6 +41,7 @@
 
     private AdaptiveBackground mBackground;
     private AlbumSetView mAlbumSetView;
+    private HudMenu mHudMenu;
     private HeadUpDisplay mHud;
     private SynchronizedHandler mHandler;
 
@@ -117,6 +118,7 @@
     @Override
     public void onPause() {
         mHandler.removeMessages(CHANGE_BACKGROUND);
+        mHudMenu.onPause();
     }
 
     @Override
@@ -142,7 +144,8 @@
 
         mRootPane.addComponent(mAlbumSetView);
         mHud = new HeadUpDisplay(mContext.getAndroidContext());
-        mHud.setMenu(new HudMenu(mContext, mSelectionManager));
+        mHudMenu = new HudMenu(mContext, mSelectionManager);
+        mHud.setMenu(mHudMenu);
         mRootPane.addComponent(mHud);
         loadBackgroundBitmap(R.drawable.square,
                 R.drawable.potrait, R.drawable.landscape);
diff --git a/new3d/src/com/android/gallery3d/data/DataManager.java b/new3d/src/com/android/gallery3d/data/DataManager.java
index fdea01a..95f01e7 100644
--- a/new3d/src/com/android/gallery3d/data/DataManager.java
+++ b/new3d/src/com/android/gallery3d/data/DataManager.java
@@ -28,6 +28,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.Map;
 
 // DataManager manages all media sets and media items in the system.
 //
@@ -212,4 +213,9 @@
         MediaSet parent = getMediaSet(parentId);
         return parent.getMediaType(uniqueId);
     }
+
+    public Map<Integer, String> getDetails(long uniqueId) {
+        // TODO: implement get details
+        return null;
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java b/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
index efe708b..fdbc3ab 100644
--- a/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
+++ b/new3d/src/com/android/gallery3d/ui/HeadUpDisplay.java
@@ -17,11 +17,7 @@
 package com.android.gallery3d.ui;
 
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.view.MotionEvent;
 import android.view.View.MeasureSpec;
 
diff --git a/new3d/src/com/android/gallery3d/ui/HudMenu.java b/new3d/src/com/android/gallery3d/ui/HudMenu.java
index 78e4ea8..013000d 100644
--- a/new3d/src/com/android/gallery3d/ui/HudMenu.java
+++ b/new3d/src/com/android/gallery3d/ui/HudMenu.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.ui;
 
+import android.app.ProgressDialog;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -24,11 +25,14 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Handler;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.GalleryContext;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.ui.MenuExecutor.MediaOperation;
+import com.android.gallery3d.ui.MenuExecutor.OnProgressUpdateListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,9 +52,63 @@
     NinePatchTexture mHighlight;
     SelectionManager mSelectionManager;
     MenuModel[] mMenuModels;
+    MenuExecutor mMenuExecutor;
     MenuItem mShare;
     MenuItem mDelete;
     MenuItem mMore;
+    ProgressUpdateDialog mDialog;
+
+    private class ProgressUpdateDialog extends ProgressDialog implements OnProgressUpdateListener {
+        Handler mUiHandler;
+        MediaOperation mOperation;
+        boolean mIsDead;
+
+        public ProgressUpdateDialog(int title, int total) {
+            super(mContext.getAndroidContext());
+            setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+            mUiHandler = new Handler();
+            setTitle(title);
+            setMax(total);
+            show();
+        }
+
+        /**
+         * The following two methods are callbacks from data thread.
+         */
+        public void onProgressUpdate(final int index, Object result) {
+            mUiHandler.post(new Runnable() {
+                public void run() {
+                    if (!mIsDead) setProgress(index);
+                }
+            });
+        }
+
+        public void onProgressComplete() {
+            // SourceMediaSet remains unchanged since SelectionManager is created so race condition
+            // between UI and data threads is not a concern here.
+            // reload has to be called from data thread.
+            mSelectionManager.getSourceMediaSet().reload();
+            // dismiss() can be called from any thread.
+            dismiss();
+        }
+
+        // This will be called when back is pressed or dismiss() is called.
+        protected void onStop() {
+            stop();
+            mSelectionManager.leaveSelectionMode();
+        }
+
+        public void stop() {
+            if (!mIsDead) {
+                mIsDead = true;
+                mOperation.cancel();
+            }
+        }
+
+        public void setOperation(MediaOperation operation) {
+            mOperation = operation;
+        }
+    }
 
     public HudMenu(GalleryContext context, SelectionManager manager) {
         mContext = context;
@@ -58,6 +116,14 @@
         mHighlight = new NinePatchTexture(context.getAndroidContext(), R.drawable.menu_highlight);
         manager.setSelectionListener(this);
         mMenuModels = new MenuModel[TOTAL_MODEL_COUNT];
+        mMenuExecutor = new MenuExecutor(context.getDataManager());
+    }
+
+    public void onPause() {
+        if (mDialog != null) {
+            mDialog.stop();
+            mDialog = null;
+        }
     }
 
     public MenuBar getTopMenuBar() {
@@ -130,6 +196,31 @@
          * Handle the menu operation here.
          */
         public void onItemSelected(int position) {
+            MenuItem item = (MenuItem) getView(position);
+            int title;
+            int action = item.getItemId();
+            switch (action) {
+                case MenuExecutor.ACTION_DELETE:
+                    title = R.string.delete;
+                    break;
+                case MenuExecutor.ACTION_ROTATE_CW:
+                    title = R.string.rotate_right;
+                    break;
+                case MenuExecutor.ACTION_ROTATE_CCW:
+                    title = R.string.rotate_left;
+                    break;
+                case MenuExecutor.ACTION_DETAILS:
+                    title = R.string.details;
+                    break;
+                default:
+                    return;
+            }
+
+            ArrayList<Long> ids = mSelectionManager.getSelected(false);
+            if (mDialog != null) mDialog.stop();
+            mDialog = new ProgressUpdateDialog(title, ids.size());
+            MediaOperation operation = mMenuExecutor.startMediaOperation(action, ids, mDialog);
+            mDialog.setOperation(operation);
         }
     }
 
@@ -182,7 +273,9 @@
             ArrayList<Uri> uris = new ArrayList<Uri>(items.size());
             DataManager manager = mContext.getDataManager();
             for (Long id : items) {
-                uris.add(manager.getMediaItemUri(id));
+                if ((manager.getSupportedOperations(id) & MediaSet.SUPPORT_SHARE) != 0) {
+                    uris.add(manager.getMediaItemUri(id));
+                }
             }
 
             if (uris.isEmpty()) {
@@ -216,7 +309,7 @@
                 , mHighlight);
         mMenuModels[DELETE_MODEL] = new MenuModel(new MenuItem[] {
                 new MenuItem(context, R.drawable.icon_delete, R.string.confirm_delete,
-                        mHighlight),
+                        mHighlight, MenuExecutor.ACTION_DELETE),
                 new MenuItem(context, R.drawable.icon_cancel, R.string.cancel,
                         mHighlight)
          });
@@ -226,11 +319,11 @@
         mMore = new MenuItem(context, R.drawable.icon_more, R.string.more, mHighlight);
         mMenuModels[MORE_MODEL] = new MenuModel(new MenuItem[] {
                 new MenuItem(context, R.drawable.icon_details, R.string.details,
-                        mHighlight),
+                        mHighlight, MenuExecutor.ACTION_DETAILS),
                 new MenuItem(context, R.drawable.icon_details, R.string.rotate_right,
-                        mHighlight),
+                        mHighlight, MenuExecutor.ACTION_ROTATE_CW),
                 new MenuItem(context, R.drawable.icon_details, R.string.rotate_left,
-                        mHighlight),
+                        mHighlight, MenuExecutor.ACTION_ROTATE_CCW),
         });
         mBottomBar.addComponent(mMore);
     }
diff --git a/new3d/src/com/android/gallery3d/ui/MenuExecutor.java b/new3d/src/com/android/gallery3d/ui/MenuExecutor.java
new file mode 100644
index 0000000..eb2fe0e
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -0,0 +1,123 @@
+/*
+ * 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.os.Handler;
+
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.FutureTask;
+
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+public class MenuExecutor {
+    private static final String TAG = "MenuExecutor";
+
+    public static final int ACTION_DELETE = 1;
+    public static final int ACTION_DETAILS = 2;
+    public static final int ACTION_ROTATE_CW = 3;
+    public static final int ACTION_ROTATE_CCW = 4;
+
+    private Handler mHandler;
+    private DataManager mDataManager;
+
+    public MenuExecutor(DataManager manager) {
+        mDataManager = manager;
+        mHandler = new Handler(manager.getDataLooper());
+    }
+
+    public static interface OnProgressUpdateListener {
+        public void onProgressUpdate(int index, Object result);
+        public void onProgressComplete();
+    }
+
+    public class MediaOperation implements Callable<Void>, FutureListener<Void> {
+        ArrayList<Long> mItems;
+        int mOperation;
+        int mIndex;
+        FutureTask<Void> mTask;
+        OnProgressUpdateListener mProgressUpdater;
+
+        public MediaOperation(int operation, ArrayList<Long> items,
+                OnProgressUpdateListener progressUpdater) {
+            mItems = items;
+            mIndex = 0;
+            mOperation = operation;
+            mProgressUpdater = progressUpdater;
+        }
+
+        public Void call() throws Exception {
+            for (long id : mItems) {
+                if (mTask.isCancelled()) return null;
+
+                Object result = null;
+                switch (mOperation) {
+                    case ACTION_DELETE:
+                        mDataManager.delete(id);
+                        break;
+                    case ACTION_ROTATE_CW:
+                        mDataManager.rotate(id, -90);
+                        break;
+                    case ACTION_ROTATE_CCW:
+                        mDataManager.rotate(id, 90);
+                        break;
+                    case ACTION_DETAILS:
+                        result = mDataManager.getDetails(id);
+                        break;
+                    default:
+                        throw new UnsupportedOperationException();
+                }
+                mProgressUpdater.onProgressUpdate(mIndex++, result);
+            }
+            return null;
+        }
+
+        public void setTask(FutureTask<Void> task) {
+            mTask = task;
+        }
+
+        public void onFutureDone(Future<? extends Void> future) {
+            mProgressUpdater.onProgressComplete();
+        }
+
+        public void cancel() {
+            mTask.requestCancel();
+            try {
+                mTask.get();
+            } catch (CancellationException ce) {
+                // ignore it.
+            } catch (ExecutionException ee) {
+                // ignore it.
+            } catch (InterruptedException ie) {
+                // ignore it.
+            }
+        }
+    }
+
+    public MediaOperation startMediaOperation(int opcode, ArrayList<Long> items,
+            final OnProgressUpdateListener listener) {
+        MediaOperation operation = new MediaOperation(opcode, items, listener);
+        FutureTask<Void> task = new FutureTask<Void>(operation, operation);
+        operation.setTask(task);
+        mHandler.post(task);
+        return operation;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/MenuItem.java b/new3d/src/com/android/gallery3d/ui/MenuItem.java
index 4e135aa..8356c23 100644
--- a/new3d/src/com/android/gallery3d/ui/MenuItem.java
+++ b/new3d/src/com/android/gallery3d/ui/MenuItem.java
@@ -20,18 +20,29 @@
 import android.graphics.Rect;
 
 public class MenuItem extends IconLabel {
+    private static final int ACTION_NONE = 0;
     private boolean mSelected;
     private Texture mHighlight;
+    private int mItemId;
 
     public MenuItem(Context context, int icon, int label, Texture highlight) {
+        this(context, icon, label, highlight, ACTION_NONE);
+    }
+
+    public MenuItem(Context context, int icon, int label, Texture highlight, int itemId) {
         super(context, icon, label);
         mHighlight = highlight;
+        mItemId = itemId;
     }
 
     public MenuItem(Context context, BasicTexture texture, String label) {
         super(context, texture, label);
     }
 
+    public int getItemId() {
+        return mItemId;
+    }
+
     public void setHighlight(Texture texture) {
         mHighlight = texture;
     }