/*
 * Copyright (C) 2007 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.camera;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.android.camera.gallery.IImage;
import com.android.camera.gallery.IImageList;
import com.android.camera.gallery.VideoObject;

import java.util.ArrayList;
import java.util.HashSet;

public class ImageGallery extends Activity implements
        GridViewSpecial.Listener, GridViewSpecial.DrawAdapter {
    private static final String STATE_SCROLL_POSITION = "scroll_position";
    private static final String STATE_SELECTED_INDEX = "first_index";

    private static final String TAG = "ImageGallery";
    private static final float INVALID_POSITION = -1f;
    private ImageManager.ImageListParam mParam;
    private IImageList mAllImages;
    private int mInclusion;
    boolean mSortAscending = false;
    private View mNoImagesView;
    public static final int CROP_MSG = 2;

    private Dialog mMediaScanningDialog;
    private MenuItem mSlideShowItem;
    private SharedPreferences mPrefs;
    private long mVideoSizeLimit = Long.MAX_VALUE;
    private View mFooterOrganizeView;

    private BroadcastReceiver mReceiver = null;

    private final Handler mHandler = new Handler();
    private boolean mLayoutComplete;
    private boolean mPausing = true;
    private ImageLoader mLoader;
    private GridViewSpecial mGvs;

    private Uri mCropResultUri;

    // The index of the first picture in GridViewSpecial.
    private int mSelectedIndex = GridViewSpecial.INDEX_NONE;
    private float mScrollPosition = INVALID_POSITION;
    private boolean mConfigurationChanged = false;

    private HashSet<IImage> mMultiSelected = null;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Must be called before setContentView().
        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

        setContentView(R.layout.image_gallery);

        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
                R.layout.custom_gallery_title);

        mNoImagesView = findViewById(R.id.no_images);

        mGvs = (GridViewSpecial) findViewById(R.id.grid);
        mGvs.setListener(this);

        mFooterOrganizeView = findViewById(R.id.footer_organize);

        // consume all click events on the footer view
        mFooterOrganizeView.setOnClickListener(Util.getNullOnClickListener());
        initializeFooterButtons();

        if (isPickIntent()) {
            mVideoSizeLimit = getIntent().getLongExtra(
                    MediaStore.EXTRA_SIZE_LIMIT, Long.MAX_VALUE);
        } else {
            mVideoSizeLimit = Long.MAX_VALUE;
            mGvs.setOnCreateContextMenuListener(
                    new CreateContextMenuListener());
        }

        setupInclusion();

        mLoader = new ImageLoader(getContentResolver(), mHandler);
    }

    private void initializeFooterButtons() {
        Button deleteButton = (Button) findViewById(R.id.button_delete);
        deleteButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                onDeleteMultipleClicked();
            }
        });

        Button shareButton = (Button) findViewById(R.id.button_share);
        shareButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                onShareMultipleClicked();
            }
        });

        Button closeButton = (Button) findViewById(R.id.button_close);
        closeButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                closeMultiSelectMode();
            }
        });
    }

    private MenuItem addSlideShowMenu(Menu menu) {
        return menu.add(Menu.NONE, Menu.NONE, MenuHelper.POSITION_SLIDESHOW,
                R.string.slide_show)
                .setOnMenuItemClickListener(
                new MenuItem.OnMenuItemClickListener() {
                    public boolean onMenuItemClick(MenuItem item) {
                        return onSlideShowClicked();
                    }
                }).setIcon(android.R.drawable.ic_menu_slideshow);
    }

    public boolean onSlideShowClicked() {
        if (!canHandleEvent()) {
            return false;
        }
        IImage img = getCurrentImage();
        if (img == null) {
            img = mAllImages.getImageAt(0);
            if (img == null) {
                return true;
            }
        }
        Uri targetUri = img.fullSizeImageUri();
        Uri thisUri = getIntent().getData();
        if (thisUri != null) {
            String bucket = thisUri.getQueryParameter("bucketId");
            if (bucket != null) {
                targetUri = targetUri.buildUpon()
                        .appendQueryParameter("bucketId", bucket)
                        .build();
            }
        }
        Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
        intent.putExtra("slideshow", true);
        startActivity(intent);
        return true;
    }

    private final Runnable mDeletePhotoRunnable = new Runnable() {
        public void run() {
            if (!canHandleEvent()) return;

            IImage currentImage = getCurrentImage();

            // The selection will be cleared when mGvs.stop() is called, so
            // we need to call getCurrentImage() before mGvs.stop().
            mGvs.stop();

            if (currentImage != null) {
                mAllImages.removeImage(currentImage);
            }
            mGvs.setImageList(mAllImages);
            mGvs.start();

            mNoImagesView.setVisibility(mAllImages.isEmpty()
                    ? View.VISIBLE
                    : View.GONE);
        }
    };

    private Uri getCurrentImageUri() {
        IImage image = getCurrentImage();
        if (image != null) {
            return image.fullSizeImageUri();
        } else {
            return null;
        }
    }

    private IImage getCurrentImage() {
        int currentSelection = mGvs.getCurrentSelection();
        if (currentSelection < 0
                || currentSelection >= mAllImages.getCount()) {
            return null;
        } else {
            return mAllImages.getImageAt(currentSelection);
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mConfigurationChanged = true;
    }

    boolean canHandleEvent() {
        // Don't process event in pause state.
        return (!mPausing) && (mLayoutComplete);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (!canHandleEvent()) return false;
        switch (keyCode) {
            case KeyEvent.KEYCODE_DEL:
                IImage image = getCurrentImage();
                if (image != null) {
                    MenuHelper.deleteImage(
                            this, mDeletePhotoRunnable, getCurrentImage());
                }
                return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    private boolean isPickIntent() {
        String action = getIntent().getAction();
        return (Intent.ACTION_PICK.equals(action)
                || Intent.ACTION_GET_CONTENT.equals(action));
    }

    private void launchCropperOrFinish(IImage img) {
        Bundle myExtras = getIntent().getExtras();

        long size = MenuHelper.getImageFileSize(img);
        if (size < 0) {
            // Return if the image file is not available.
            return;
        }

        if (size > mVideoSizeLimit) {
            DialogInterface.OnClickListener buttonListener =
                    new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            };
            new AlertDialog.Builder(this)
                    .setIcon(android.R.drawable.ic_dialog_info)
                    .setTitle(R.string.file_info_title)
                    .setMessage(R.string.video_exceed_mms_limit)
                    .setNeutralButton(R.string.details_ok, buttonListener)
                    .show();
            return;
        }

        String cropValue = myExtras != null ? myExtras.getString("crop") : null;
        if (cropValue != null) {
            Bundle newExtras = new Bundle();
            if (cropValue.equals("circle")) {
                newExtras.putString("circleCrop", "true");
            }

            Intent cropIntent = new Intent();
            cropIntent.setData(img.fullSizeImageUri());
            cropIntent.setClass(this, CropImage.class);
            cropIntent.putExtras(newExtras);

            /* pass through any extras that were passed in */
            cropIntent.putExtras(myExtras);
            startActivityForResult(cropIntent, CROP_MSG);
        } else {
            Intent result = new Intent(null, img.fullSizeImageUri());
            if (myExtras != null && myExtras.getBoolean("return-data")) {
                // The size of a transaction should be below 100K.
                Bitmap bitmap = img.fullSizeBitmap(
                        IImage.UNCONSTRAINED, 100 * 1024);
                if (bitmap != null) {
                    result.putExtra("data", bitmap);
                }
            }
            setResult(RESULT_OK, result);
            finish();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        switch (requestCode) {
            case MenuHelper.RESULT_COMMON_MENU_CROP: {
                if (resultCode == RESULT_OK) {

                    // The CropImage activity passes back the Uri of the cropped
                    // image as the Action rather than the Data.
                    // We store this URI so we can move the selection box to it
                    // later.
                    mCropResultUri = Uri.parse(data.getAction());
                }
                break;
            }
            case CROP_MSG: {
                if (resultCode == RESULT_OK) {
                    setResult(resultCode, data);
                    finish();
                }
                break;
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mPausing = true;

        mLoader.stop();

        mGvs.stop();

        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
            mReceiver = null;
        }

        // Now that we've paused the threads that are using the cursor it is
        // safe to close it.
        mAllImages.close();
        mAllImages = null;
    }

    private void rebake(boolean unmounted, boolean scanning) {
        mGvs.stop();
        if (mAllImages != null) {
            mAllImages.close();
            mAllImages = null;
        }

        if (mMediaScanningDialog != null) {
            mMediaScanningDialog.cancel();
            mMediaScanningDialog = null;
        }

        if (scanning) {
            mMediaScanningDialog = ProgressDialog.show(
                    this,
                    null,
                    getResources().getString(R.string.wait),
                    true,
                    true);
        }

        mParam = allImages(!unmounted && !scanning);
        mAllImages = ImageManager.makeImageList(getContentResolver(), mParam);

        mGvs.setImageList(mAllImages);
        mGvs.setDrawAdapter(this);
        mGvs.setLoader(mLoader);
        mGvs.start();
        mNoImagesView.setVisibility(mAllImages.getCount() > 0
                ? View.GONE
                : View.VISIBLE);
    }

    @Override
    protected void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        state.putFloat(STATE_SCROLL_POSITION, mScrollPosition);
        state.putInt(STATE_SELECTED_INDEX, mSelectedIndex);
    }

    @Override
    protected void onRestoreInstanceState(Bundle state) {
        super.onRestoreInstanceState(state);
        mScrollPosition = state.getFloat(
                STATE_SCROLL_POSITION, INVALID_POSITION);
        mSelectedIndex = state.getInt(STATE_SELECTED_INDEX, 0);
    }

    @Override
    public void onResume() {
        super.onResume();

        mGvs.setSizeChoice(Integer.parseInt(
                mPrefs.getString("pref_gallery_size_key", "1")));
        mGvs.requestFocus();

        String sortOrder = mPrefs.getString("pref_gallery_sort_key", null);
        if (sortOrder != null) {
            mSortAscending = sortOrder.equals("ascending");
        }

        mPausing = false;

        // install an intent filter to receive SD card related events.
        IntentFilter intentFilter =
                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
        intentFilter.addDataScheme("file");

        mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                    // SD card available
                    // TODO put up a "please wait" message
                    // TODO also listen for the media scanner finished message
                } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
                    // SD card unavailable
                    rebake(true, false);
                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
                    rebake(false, true);
                } else if (action.equals(
                        Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
                    rebake(false, false);
                } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
                    rebake(true, false);
                }
            }
        };
        registerReceiver(mReceiver, intentFilter);
        rebake(false, ImageManager.isMediaScannerScanning(
                getContentResolver()));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (isPickIntent()) {
            String type = getIntent().resolveType(this);
            if (type != null) {
                if (isImageType(type)) {
                    MenuHelper.addCapturePictureMenuItems(menu, this);
                } else if (isVideoType(type)) {
                    MenuHelper.addCaptureVideoMenuItems(menu, this);
                }
            }
        } else {
            MenuHelper.addCaptureMenuItems(menu, this);
            if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) {
                mSlideShowItem = addSlideShowMenu(menu);
            }

            MenuItem item = menu.add(Menu.NONE, Menu.NONE,
                    MenuHelper.POSITION_GALLERY_SETTING,
                    R.string.camerasettings);
            item.setOnMenuItemClickListener(
                    new MenuItem.OnMenuItemClickListener() {
                public boolean onMenuItemClick(MenuItem item) {
                    Intent preferences = new Intent();
                    preferences.setClass(ImageGallery.this,
                            GallerySettings.class);
                    startActivity(preferences);
                    return true;
                }
            });
            item.setAlphabeticShortcut('p');
            item.setIcon(android.R.drawable.ic_menu_preferences);

            item = menu.add(Menu.NONE, Menu.NONE,
                    MenuHelper.POSITION_MULTISELECT,
                    R.string.multiselect);
            item.setOnMenuItemClickListener(
                    new MenuItem.OnMenuItemClickListener() {
                public boolean onMenuItemClick(MenuItem item) {
                    if (isInMultiSelectMode()) {
                        closeMultiSelectMode();
                    } else {
                        openMultiSelectMode();
                    }
                    return true;
                }
            });
            item.setIcon(R.drawable.ic_menu_multiselect_gallery);
        }
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (!canHandleEvent()) return false;
        if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) {
            boolean videoSelected = isVideoSelected();
            // TODO: Only enable slide show if there is at least one image in
            // the folder.
            if (mSlideShowItem != null) {
                mSlideShowItem.setEnabled(!videoSelected);
            }
        }

        return true;
    }

    private boolean isVideoSelected() {
        IImage image = getCurrentImage();
        return (image != null) && ImageManager.isVideo(image);
    }

    private boolean isImageType(String type) {
        return type.equals("vnd.android.cursor.dir/image")
                || type.equals("image/*");
    }

    private boolean isVideoType(String type) {
        return type.equals("vnd.android.cursor.dir/video")
                || type.equals("video/*");
    }

    // According to the intent, setup what we include (image/video) in the
    // gallery and the title of the gallery.
    private void setupInclusion() {
        mInclusion = ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS;

        Intent intent = getIntent();
        if (intent != null) {
            String type = intent.resolveType(this);
            TextView leftText = (TextView) findViewById(R.id.left_text);
            if (type != null) {
                if (isImageType(type)) {
                    mInclusion = ImageManager.INCLUDE_IMAGES;
                    if (isPickIntent()) {
                        leftText.setText(R.string.pick_photos_gallery_title);
                    } else {
                        leftText.setText(R.string.photos_gallery_title);
                    }
                }
                if (isVideoType(type)) {
                    mInclusion = ImageManager.INCLUDE_VIDEOS;
                    if (isPickIntent()) {
                        leftText.setText(R.string.pick_videos_gallery_title);
                    } else {
                        leftText.setText(R.string.videos_gallery_title);
                    }
                }
            }
            Bundle extras = intent.getExtras();
            String title = (extras != null)
                    ? extras.getString("windowTitle")
                    : null;
            if (title != null && title.length() > 0) {
                leftText.setText(title);
            }

            if (extras != null) {
                mInclusion = (ImageManager.INCLUDE_IMAGES
                        | ImageManager.INCLUDE_VIDEOS)
                        & extras.getInt("mediaTypes", mInclusion);
            }

            if (extras != null && extras.getBoolean("pick-drm")) {
                Log.d(TAG, "pick-drm is true");
                mInclusion = ImageManager.INCLUDE_DRM_IMAGES;
            }
        }
    }

    // Returns the image list parameter which contains the subset of image/video
    // we want.
    private ImageManager.ImageListParam allImages(boolean storageAvailable) {
        if (!storageAvailable) {
            return ImageManager.getEmptyImageListParam();
        } else {
            Uri uri = getIntent().getData();
            return ImageManager.getImageListParam(
                    ImageManager.DataLocation.EXTERNAL,
                    mInclusion,
                    mSortAscending
                    ? ImageManager.SORT_ASCENDING
                    : ImageManager.SORT_DESCENDING,
                    (uri != null)
                    ? uri.getQueryParameter("bucketId")
                    : null);
        }
    }

    private void toggleMultiSelected(IImage image) {
        int original = mMultiSelected.size();
        if (!mMultiSelected.add(image)) {
            mMultiSelected.remove(image);
        }
        mGvs.invalidate();
        if (original == 0) showFooter();
        if (mMultiSelected.size() == 0) hideFooter();
    }

    public void onImageClicked(int index) {
        if (index < 0 || index >= mAllImages.getCount()) {
            return;
        }
        mSelectedIndex = index;
        mGvs.setSelectedIndex(index);

        IImage image = mAllImages.getImageAt(index);

        if (isInMultiSelectMode()) {
            toggleMultiSelected(image);
            return;
        }

        if (isPickIntent()) {
            launchCropperOrFinish(image);
        } else {
            Intent intent;
            if (image instanceof VideoObject) {
                intent = new Intent(
                        Intent.ACTION_VIEW, image.fullSizeImageUri());
                intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
                        ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            } else {
                intent = new Intent(this, ViewImage.class);
                intent.putExtra(ViewImage.KEY_IMAGE_LIST, mParam);
                intent.setData(image.fullSizeImageUri());
            }
            startActivity(intent);
        }
    }

    public void onImageTapped(int index) {
        // In the multiselect mode, once the finger finishes tapping, we hide
        // the selection box by setting the selected index to none. However, if
        // we use the dpad center key, we will keep the selected index in order
        // to show the the selection box. We do this because we have the
        // multiselect marker on the images to indicate which of them are
        // selected, so we don't need the selection box, but in the dpad case
        // we still need the selection box to show as a "cursor".

        if (isInMultiSelectMode()) {
            mGvs.setSelectedIndex(GridViewSpecial.INDEX_NONE);
            toggleMultiSelected(mAllImages.getImageAt(index));
        } else {
            onImageClicked(index);
        }
    }

    private class CreateContextMenuListener implements
            View.OnCreateContextMenuListener {
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenu.ContextMenuInfo menuInfo) {
            if (!canHandleEvent()) return;

            IImage image = getCurrentImage();

            if (image == null) {
                return;
            }

            boolean isImage = ImageManager.isImage(image);
            if (isImage) {
                menu.add(R.string.view)
                        .setOnMenuItemClickListener(
                        new MenuItem.OnMenuItemClickListener() {
                            public boolean onMenuItemClick(MenuItem item) {
                                if (!canHandleEvent()) return false;
                                onImageClicked(mGvs.getCurrentSelection());
                                return true;
                            }
                        });
            }

            menu.setHeaderTitle(isImage
                    ? R.string.context_menu_header
                    : R.string.video_context_menu_header);
            if ((mInclusion & (ImageManager.INCLUDE_IMAGES
                    | ImageManager.INCLUDE_VIDEOS)) != 0) {
                MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems(
                        menu,
                        MenuHelper.INCLUDE_ALL,
                        ImageGallery.this,
                        mHandler,
                        mDeletePhotoRunnable,
                        new MenuHelper.MenuInvoker() {
                            public void run(MenuHelper.MenuCallback cb) {
                                if (!canHandleEvent()) {
                                    return;
                                }
                                cb.run(getCurrentImageUri(), getCurrentImage());
                                mGvs.invalidateImage(mGvs.getCurrentSelection());
                            }
                        });

                if (r != null) {
                    r.gettingReadyToOpen(menu, image);
                }

                if (isImage) {
                    MenuHelper.enableShowOnMapMenuItem(
                            menu, MenuHelper.hasLatLngData(image));
                    addSlideShowMenu(menu);
                }
            }
        }
    }

    public void onLayoutComplete(boolean changed) {
        mLayoutComplete = true;
        if (mCropResultUri != null) {
            IImage image = mAllImages.getImageForUri(mCropResultUri);
            mCropResultUri = null;
            if (image != null) {
                mSelectedIndex = mAllImages.getImageIndex(image);
            }
        }
        mGvs.setSelectedIndex(mSelectedIndex);
        if (mScrollPosition == INVALID_POSITION) {
            if (mSortAscending) {
                mGvs.scrollTo(0, mGvs.getHeight());
            } else {
                mGvs.scrollToImage(0);
            }
        } else if (mConfigurationChanged) {
            mConfigurationChanged = false;
            mGvs.scrollTo(mScrollPosition);
            if (mGvs.getCurrentSelection() != GridViewSpecial.INDEX_NONE) {
                mGvs.scrollToVisible(mSelectedIndex);
            }
        } else {
            mGvs.scrollTo(mScrollPosition);
        }
    }

    public void onScroll(float scrollPosition) {
        mScrollPosition = scrollPosition;
    }

    private Drawable mVideoOverlay;
    private Drawable mVideoMmsErrorOverlay;
    private Drawable mMultiSelectTrue;
    private Drawable mMultiSelectFalse;

    // mSrcRect and mDstRect are only used in drawImage, but we put them as
    // instance variables to reduce the memory allocation overhead because
    // drawImage() is called a lot.
    private final Rect mSrcRect = new Rect();
    private final Rect mDstRect = new Rect();

    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);

    public void drawImage(Canvas canvas, IImage image,
            Bitmap b, int xPos, int yPos, int w, int h) {
        if (b != null) {
            // if the image is close to the target size then crop,
            // otherwise scale both the bitmap and the view should be
            // square but I suppose that could change in the future.

            int bw = b.getWidth();
            int bh = b.getHeight();

            int deltaW = bw - w;
            int deltaH = bh - h;

            if (deltaW >= 0 && deltaW < 10 &&
                deltaH >= 0 && deltaH < 10) {
                int halfDeltaW = deltaW / 2;
                int halfDeltaH = deltaH / 2;
                mSrcRect.set(0 + halfDeltaW, 0 + halfDeltaH,
                        bw - halfDeltaW, bh - halfDeltaH);
                mDstRect.set(xPos, yPos, xPos + w, yPos + h);
                canvas.drawBitmap(b, mSrcRect, mDstRect, null);
            } else {
                mSrcRect.set(0, 0, bw, bh);
                mDstRect.set(xPos, yPos, xPos + w, yPos + h);
                canvas.drawBitmap(b, mSrcRect, mDstRect, mPaint);
            }
        } else {
            // If the thumbnail cannot be drawn, put up an error icon
            // instead
            Bitmap error = getErrorBitmap(image);
            int width = error.getWidth();
            int height = error.getHeight();
            mSrcRect.set(0, 0, width, height);
            int left = (w - width) / 2 + xPos;
            int top = (w - height) / 2 + yPos;
            mDstRect.set(left, top, left + width, top + height);
            canvas.drawBitmap(error, mSrcRect, mDstRect, null);
        }

        if (ImageManager.isVideo(image)) {
            Drawable overlay = null;
            long size = MenuHelper.getImageFileSize(image);
            if (size >= 0 && size <= mVideoSizeLimit) {
                if (mVideoOverlay == null) {
                    mVideoOverlay = getResources().getDrawable(
                            R.drawable.ic_gallery_video_overlay);
                }
                overlay = mVideoOverlay;
            } else {
                if (mVideoMmsErrorOverlay == null) {
                    mVideoMmsErrorOverlay = getResources().getDrawable(
                            R.drawable.ic_error_mms_video_overlay);
                }
                overlay = mVideoMmsErrorOverlay;
                Paint paint = new Paint();
                paint.setARGB(0x80, 0x00, 0x00, 0x00);
                canvas.drawRect(xPos, yPos, xPos + w, yPos + h, paint);
            }
            int width = overlay.getIntrinsicWidth();
            int height = overlay.getIntrinsicHeight();
            int left = (w - width) / 2 + xPos;
            int top = (h - height) / 2 + yPos;
            mSrcRect.set(left, top, left + width, top + height);
            overlay.setBounds(mSrcRect);
            overlay.draw(canvas);
        }
    }

    public boolean needsDecoration() {
        return (mMultiSelected != null);
    }

    public void drawDecoration(Canvas canvas, IImage image,
            int xPos, int yPos, int w, int h) {
        if (mMultiSelected != null) {
            initializeMultiSelectDrawables();

            Drawable checkBox = mMultiSelected.contains(image)
                    ? mMultiSelectTrue
                    : mMultiSelectFalse;
            int width = checkBox.getIntrinsicWidth();
            int height = checkBox.getIntrinsicHeight();
            int left = 5 + xPos;
            int top = h - height - 5 + yPos;
            mSrcRect.set(left, top, left + width, top + height);
            checkBox.setBounds(mSrcRect);
            checkBox.draw(canvas);
        }
    }

    private void initializeMultiSelectDrawables() {
        if (mMultiSelectTrue == null) {
            mMultiSelectTrue = getResources()
                    .getDrawable(R.drawable.btn_check_buttonless_on);
        }
        if (mMultiSelectFalse == null) {
            mMultiSelectFalse = getResources()
                    .getDrawable(R.drawable.btn_check_buttonless_off);
        }
    }

    private Bitmap mMissingImageThumbnailBitmap;
    private Bitmap mMissingVideoThumbnailBitmap;

    // Create this bitmap lazily, and only once for all the ImageBlocks to
    // use
    public Bitmap getErrorBitmap(IImage image) {
        if (ImageManager.isImage(image)) {
            if (mMissingImageThumbnailBitmap == null) {
                mMissingImageThumbnailBitmap = BitmapFactory.decodeResource(
                        getResources(),
                        R.drawable.ic_missing_thumbnail_picture);
            }
            return mMissingImageThumbnailBitmap;
        } else {
            if (mMissingVideoThumbnailBitmap == null) {
                mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource(
                        getResources(), R.drawable.ic_missing_thumbnail_video);
            }
            return mMissingVideoThumbnailBitmap;
        }
    }

    private Animation mFooterAppear;
    private Animation mFooterDisappear;

    private void showFooter() {
        mFooterOrganizeView.setVisibility(View.VISIBLE);
        if (mFooterAppear == null) {
            mFooterAppear = AnimationUtils.loadAnimation(
                    this, R.anim.footer_appear);
        }
        mFooterOrganizeView.startAnimation(mFooterAppear);
    }

    private void hideFooter() {
        if (mFooterOrganizeView.getVisibility() != View.GONE) {
            mFooterOrganizeView.setVisibility(View.GONE);
            if (mFooterDisappear == null) {
                mFooterDisappear = AnimationUtils.loadAnimation(
                        this, R.anim.footer_disappear);
            }
            mFooterOrganizeView.startAnimation(mFooterDisappear);
        }
    }

    private String getShareMultipleMimeType() {
        final int FLAG_IMAGE = 1, FLAG_VIDEO = 2;
        int flag = 0;
        for (IImage image : mMultiSelected) {
            flag |= ImageManager.isImage(image) ? FLAG_IMAGE : FLAG_VIDEO;
        }
        return flag == FLAG_IMAGE
                ? "image/*"
                : flag == FLAG_VIDEO ? "video/*" : "*/*";
    }

    private void onShareMultipleClicked() {
        if (mMultiSelected.size() > 1) {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_SEND_MULTIPLE);

            String mimeType = getShareMultipleMimeType();
            intent.setType(mimeType);
            ArrayList<Parcelable> list = new ArrayList<Parcelable>();
            for (IImage image : mMultiSelected) {
                list.add(image.fullSizeImageUri());
            }
            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, list);
            try {
                startActivity(Intent.createChooser(
                        intent, getText(R.string.send_media_files)));
            } catch (android.content.ActivityNotFoundException ex) {
                Toast.makeText(this, R.string.no_way_to_share,
                        Toast.LENGTH_SHORT).show();
            }
        } else if (mMultiSelected.size() == 1) {
            IImage image = mMultiSelected.iterator().next();
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_SEND);
            String mimeType = image.getMimeType();
            intent.setType(mimeType);
            intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
            boolean isImage = ImageManager.isImage(image);
            try {
                startActivity(Intent.createChooser(intent, getText(
                        isImage ? R.string.sendImage : R.string.sendVideo)));
            } catch (android.content.ActivityNotFoundException ex) {
                Toast.makeText(this, isImage
                        ? R.string.no_way_to_share_image
                        : R.string.no_way_to_share_video,
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void onDeleteMultipleClicked() {
        Runnable action = new Runnable() {
            public void run() {
                ArrayList<Uri> uriList = new ArrayList<Uri>();
                for (IImage image : mMultiSelected) {
                    uriList.add(image.fullSizeImageUri());
                }
                closeMultiSelectMode();
                Intent intent = new Intent(ImageGallery.this,
                        DeleteImage.class);
                intent.putExtra("delete-uris", uriList);
                try {
                    startActivity(intent);
                } catch (ActivityNotFoundException ex) {
                    Log.e(TAG, "Delete images fail", ex);
                }
            }
        };
        MenuHelper.deleteMultiple(this, action);
    }

    private boolean isInMultiSelectMode() {
        return mMultiSelected != null;
    }

    private void closeMultiSelectMode() {
        if (mMultiSelected == null) return;
        mMultiSelected = null;
        mGvs.invalidate();
        hideFooter();
    }

    private void openMultiSelectMode() {
        if (mMultiSelected != null) return;
        mMultiSelected = new HashSet<IImage>();
        mGvs.invalidate();
    }

}