uk.org.downiesoft.slideshow.GridViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.downiesoft.slideshow.GridViewFragment.java

Source

/* 
 Copyright 2014-2016 Alan G. Downie
    
 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 uk.org.downiesoft.slideshow;

import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import android.graphics.PointF;

/**
 * Fragment class to manage thumbnail {@link android.widget.GridView} and associated {@link PreviewFragment} image preview pane.
 */
public class GridViewFragment extends Fragment implements FavouritesManager.FavouritesCallback,
        ThumbnailAdapter.ThumbnailAdapterListener, PreviewFragment.PreviewFragmentListener {

    public static final String TAG = GridViewFragment.class.getName();

    /** Reference to the layout GridView. */
    private GridView mGridView;
    /** Reference to the layout ProgressBar. */
    private ProgressBar mProgressBar;
    /** The {@link ThumbnailAdapter} backing the GridView. */
    private ThumbnailAdapter mThumbnailAdapter;
    /** The current source directory for the images. */
    private ZFile mCurrentFile;
    /** The previous source directory for images (used when returning from viewing favourites). */
    private ZFile mPreviousFile;
    /** The currently selected image (shown in preview pane). */
    private int mCurrentImage;
    /** The current first position visible in the grid. */
    private int mCurrentScrollY;
    /** Flag indicating if back key should be intercepted. */
    private boolean mGuardBackKey = false;
    /** Maximumn time between back key presses to cause a "normal" back event. */
    private long mBackTimestampMillis = 0;
    /** A reference to the fragment managing the preview frame. */
    private PreviewFragment mPreview;
    /** The current favourites list. */
    private ListPresentation mFavourites;
    /** Flag indicating if we are showing the current favourites list. */
    private boolean mShowingFavourites;
    /** The application preferences. */
    private SharedPreferences mSettings;
    /** The size of thumbnails in pixels. */
    private int mThumbSize;
    /** The action mode triggered by a long press. */
    private ActionMode mActionMode;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SlideShowActivity.debug(1, TAG, "onCreate: %d %s", mCurrentImage,
                savedInstanceState == null ? "null" : savedInstanceState.toString());
        mSettings = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mThumbSize = getThumbSizeSetting();
        mCurrentScrollY = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        SlideShowActivity.debug(1, TAG, "onCreateView: %d %s", mCurrentImage,
                savedInstanceState == null ? "null" : savedInstanceState.toString());
        View parent = inflater.inflate(R.layout.gridview_fragment, container, false);
        mGridView = (GridView) parent.findViewById(R.id.gridview);
        mGridView.setEmptyView(parent.findViewById(R.id.emtpygridview));
        mProgressBar = (ProgressBar) parent.findViewById(R.id.gridviewProgressBar);
        mThumbSize = getThumbSizeSetting();
        mGridView.setColumnWidth(mThumbSize);
        mPreview = (PreviewFragment) getFragmentManager().findFragmentByTag(PreviewFragment.TAG);
        setHasOptionsMenu(true);
        // define actions to be taken when a thumbnail is clicked
        mGridView.setOnItemClickListener(new GridView.OnItemClickListener() {
            /**
             * {@inheritDoc}
             */
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mPreview == null || mGridView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
                    // Preview not visible so switch to slidshow view
                    mCurrentImage = position;
                    mGridView.setItemChecked(position, true);
                    ActivityOptions options = ActivityOptions.makeScaleUpAnimation(mGridView, (int) view.getX(),
                            (int) view.getY(), view.getWidth(), view.getHeight());
                    startSlideshow(mCurrentFile, position, false, options);
                } else {
                    // Preview visible so action depends on which icon was clicked
                    if (mCurrentImage == position) {
                        // currently selected icon so switch to slideshow view
                        ActivityOptions options = ActivityOptions.makeScaleUpAnimation(mGridView, (int) view.getX(),
                                (int) view.getY(), view.getWidth(), view.getHeight());
                        startSlideshow(mCurrentFile, position, false, options);
                    } else {
                        // different icon so select it
                        mCurrentImage = position;
                        mCurrentScrollY = mGridView.getFirstVisiblePosition();
                        mGridView.setItemChecked(position, true);
                        mPreview.setImage(mCurrentImage);
                    }
                }
            }

        });
        mGridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            /**
             * {@inheritDoc}
             */
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                // begin action mode
                mGridView.setMultiChoiceModeListener(new GridViewMultiChoiceModeListener());
                mGridView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
                mGridView.setItemChecked(position, true);
                return true;
            }
        });
        if (savedInstanceState != null) {
            SlideShowActivity.debug(1, TAG, "savedInstance: %s", savedInstanceState.toString());
            mShowingFavourites = savedInstanceState.getBoolean("showingFavourites");
            mCurrentFile = new ZFile(savedInstanceState.getString("currentFile", ""));
            if (mCurrentFile.toString().endsWith(getString(R.string.text_images_placeholder))) {
                mCurrentFile = new ZFile(mCurrentFile.getParentPath());
            }
            mCurrentImage = savedInstanceState.getInt("currentImage", 0);
            mPreviousFile = new ZFile(savedInstanceState.getString("previousFile"));
            if (mThumbnailAdapter == null) {
                mThumbnailAdapter = new ThumbnailAdapter(getActivity(), mCurrentFile, mCurrentImage, mThumbSize,
                        this);
            }
        } else {
            String defaultPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                    .toString();
            mCurrentFile = new ZFile(mSettings.getString(SettingsActivity.PREFS_LASTDIR, defaultPath));
            if (mCurrentFile.toString().endsWith(getString(R.string.text_images_placeholder))) {
                mCurrentFile = new ZFile(mCurrentFile.getParentPath());
            }
            if (!mCurrentFile.exists()) {
                mCurrentFile = new ZFile(defaultPath);
                mCurrentImage = mSettings.getInt(SettingsActivity.PREFS_CURRENTIMAGE, 0);
            }
            mPreviousFile = mCurrentFile;
            mThumbnailAdapter = new ThumbnailAdapter(getActivity(), mCurrentFile, mCurrentImage, mThumbSize, this);
        }
        mFavourites = FavouritesManager.getInstance(getActivity());
        if (savedInstanceState != null) {
            SlideShowActivity.debug(1, TAG, "onCreateView: %s", savedInstanceState.toString());
            mCurrentImage = savedInstanceState.getInt("currentImage", 0);
        }
        SlideShowActivity.debug(1, TAG, "onCreateView end: %d", mCurrentImage);
        return parent;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onViewStateRestored(Bundle outState) {
        super.onViewStateRestored(outState);
        SlideShowActivity.debug(1, TAG, "onViewStateRestored: %d %s", mCurrentImage,
                outState == null ? "null" : outState.toString());
        if (outState != null) {
            mCurrentImage = outState.getInt("currentImage", 0);
        } else {
            mCurrentImage = mSettings.getInt(SettingsActivity.PREFS_CURRENTIMAGE, 0);
            mCurrentScrollY = mSettings.getInt(SettingsActivity.PREFS_FIRSTIMAGE, 0);
        }
        if (mThumbnailAdapter == null) {
            mThumbnailAdapter = new ThumbnailAdapter(getActivity(), mCurrentFile, mCurrentImage, mThumbSize, this);
        }
        if (mPreview != null) {
            mPreview.setPresentation(mThumbnailAdapter.getPresentation(), mCurrentImage);
        }
        mGridView.setAdapter(mThumbnailAdapter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onStart() {
        super.onStart();
        SlideShowActivity.debug(1, TAG, "onStart: %d %d", mCurrentImage, mCurrentScrollY);
        int prevThumbSize = mThumbSize;
        // check if thumbsize has changed (e.g. if returning from settings activity)
        mThumbSize = getThumbSizeSetting();
        mThumbnailAdapter.setThumbSize(mThumbSize);
        if (mThumbSize != prevThumbSize) {
            mGridView.setColumnWidth(mThumbSize);
            if (mThumbnailAdapter != null) {
                mThumbnailAdapter.notifyDataSetChanged();
            }
            mGridView.invalidate();
        }
        boolean visible = mSettings.getBoolean(getActivity().getString(R.string.PREFS_SHOW_PREVIEW),
                mPreview != null);
        mGridView.setChoiceMode(
                (mPreview == null || !visible) ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_SINGLE);
        mGridView.setSelection(mCurrentImage);
        mGridView.setSelection(mCurrentScrollY);
        mGridView.setItemChecked(mCurrentImage, true);
        mGridView.invalidate();
        setStatus(mCurrentFile);
        mThumbnailAdapter.resume();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onResume() {
        super.onResume();
        SlideShowActivity.debug(1, TAG, "onResume: %d %d", mCurrentImage, mCurrentScrollY);
        mGuardBackKey = true;
        // ensure the visible scroll region is in the same place as before
        mGridView.setSelection(mCurrentImage);
        mGridView.setSelection(mCurrentScrollY);
        mGridView.setItemChecked(mCurrentImage, true);
        mGridView.invalidate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPause() {
        super.onPause();
        SlideShowActivity.debug(1, TAG, "onPause: %d %d", mCurrentImage, mCurrentScrollY);
        mGuardBackKey = false;
        mCurrentScrollY = mGridView.getFirstVisiblePosition();
        mFavourites.save(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onStop() {
        super.onStop();
        SlideShowActivity.debug(1, TAG, "onStop: %d", mCurrentImage);
        SharedPreferences.Editor editor = mSettings.edit();
        if (!mShowingFavourites) {
            editor.putString(SettingsActivity.PREFS_LASTDIR, mCurrentFile.toString());
            editor.putInt(SettingsActivity.PREFS_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FIRSTIMAGE, mCurrentScrollY);
        } else {
            editor.putInt(SettingsActivity.PREFS_FAV_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FAV_FIRSTIMAGE, mCurrentScrollY);
        }
        editor.apply();
        if (mThumbnailAdapter != null)
            mThumbnailAdapter.cancel();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("currentFile", mCurrentFile.getPath());
        outState.putString("previousFile", mPreviousFile.getPath());
        outState.putInt("currentImage", mCurrentImage);
        outState.putBoolean("showingFavourites", mShowingFavourites);
        SlideShowActivity.debug(1, TAG, "onSaveInstanceState: %s", outState.toString());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
        super.onCreateOptionsMenu(menu, menuInflater);
        menuInflater.inflate(R.menu.gridfragment_menu, menu);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        if (!mShowingFavourites) {
            menu.setGroupVisible(R.id.menu_group_not_favourites, true);
            menu.setGroupVisible(R.id.menu_group_favourites, false);
        } else {
            menu.setGroupVisible(R.id.menu_group_not_favourites, false);
            menu.setGroupVisible(R.id.menu_group_favourites, true);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_slideshow:
            View thumbView = mGridView.getChildAt(0);
            ActivityOptions options = ActivityOptions.makeScaleUpAnimation(mGridView, (int) thumbView.getX(),
                    (int) thumbView.getY(), thumbView.getWidth(), thumbView.getHeight());
            startSlideshow(mCurrentFile, 0, true, options);
            return true;
        case R.id.action_show_favourites:
            showFavourites();
            return true;
        case R.id.action_save_favourites:
            FavouritesManager.saveFavouritesDialog(getActivity(), mFavourites, this);
            return true;
        case R.id.action_open_favourites:
            FavouritesManager.loadFavouritesDialog(getActivity(), this);
            return true;
        case R.id.action_refresh_gridthumbs:
            mThumbnailAdapter.refreshThumbs();
            return true;
        case R.id.action_browse:
            Intent intent = new Intent(getActivity(), BrowserActivity.class);
            intent.putExtra(BrowserDialog.ARG_ZFILE, mCurrentFile.toArgs());
            startActivityForResult(intent, SlideShowActivity.REQUEST_BROWSER);
            return true;
        default:
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        SlideShowActivity.debug(1, TAG, "onActivityResult(%s,%s)", requestCode, resultCode);
        switch (requestCode) {
        case SlideShowActivity.REQUEST_FAVOURITES:
            if (resultCode == Activity.RESULT_OK) {
                File file = new File(data.getStringExtra("file"));
                onFavouritesChanged(file);
            }
            break;
        case SlideShowActivity.REQUEST_SLIDESHOW:
            if (data != null) {
                int image = data.getIntExtra("currentImage", mCurrentImage);
                SlideShowActivity.debug(1, TAG, "onActivityResult: %d %d", requestCode, image);
                if (image != mCurrentImage) {
                    mCurrentImage = image;
                    int first = mGridView.getFirstVisiblePosition();
                    int last = mGridView.getLastVisiblePosition();
                    if (image < first || image > last) {
                        mCurrentScrollY = image;
                    }
                    mGridView.setSelection(mCurrentImage);
                    if (mPreview != null) {
                        //mPreview.setImage(mCurrentImage);
                    }
                }
            }
            break;
        case SlideShowActivity.REQUEST_BROWSER:
            if (resultCode == Activity.RESULT_OK) {
                ZFile zfile = new ZFile(data.getBundleExtra(BrowserDialog.ARG_ZFILE));
                if (!zfile.equals(mCurrentFile)) {
                    fileChanged(zfile, 0, 0, false);
                }
            }
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    /**
     * Get the current thumbnail setting size.
     * @return The thumbnail size in pixels.
     */
    private int getThumbSizeSetting() {
        int thumbSize = Integer
                .parseInt(mSettings.getString(getActivity().getString(R.string.PREFS_THUMBSIZE), "1"));
        SlideShowActivity.debug(1, TAG, "thumbsize setting: %s", thumbSize);
        TypedArray thumbDimens = getResources().obtainTypedArray(R.array.thumbsize_dimensions);
        thumbSize = thumbDimens.getDimensionPixelSize(thumbSize,
                getResources().getDimensionPixelSize(R.dimen.thumbsize_medium));
        SlideShowActivity.debug(1, TAG, "thumbsize pixels: %s", thumbSize);
        thumbDimens.recycle();
        return thumbSize;
    }

    /**
     * Switch to favourites view.
     */
    public void showFavourites() {
        mFavourites.save(false);
        int currentImage = mSettings.getInt(SettingsActivity.PREFS_FAV_CURRENTIMAGE, 0);
        int currentScrollY = mSettings.getInt(SettingsActivity.PREFS_FAV_FIRSTIMAGE, 0);
        fileChanged(mFavourites.getRootFile(), currentImage, currentScrollY, true);
        getActivity().invalidateOptionsMenu();
    }

    /**
     * Start the slideshow activity.
     * @param aFile The location of the images to be shown.
     * @param aPosition The position of the starting slide.
     * @param aAutoStart Flag indicating if the slideshow should start running immediately (true).
     * @param aOptions Activity transition options (e.g. scale-up transition option).
     */
    public void startSlideshow(ZFile aFile, int aPosition, boolean aAutoStart, ActivityOptions aOptions) {
        Intent intent = new Intent(getActivity(), SlidesActivity.class);
        intent.putExtra(SlidesActivity.ARG_ZFILE, aFile.toArgs());
        intent.putExtra(SlidesActivity.ARG_START_SLIDE, aPosition);
        intent.putExtra(SlidesActivity.ARG_AUTOSTART, aAutoStart);
        if (aOptions != null) {
            startActivityForResult(intent, SlideShowActivity.REQUEST_SLIDESHOW, aOptions.toBundle());
        } else {
            startActivity(intent);
        }
    }

    /**
     * Change the grid to show the images in the specified location.
     * @param aFile The location of the images to be viewed.
     * @param aImage The initially selected image.
     * @param aFirst The first visible image in the grid view (to reinstate scroll position).
     * @param aIsFavourites Flag indicating if this is a favourites list.
     */
    public void fileChanged(ZFile aFile, int aImage, int aFirst, boolean aIsFavourites) {
        SlideShowActivity.debug(1, TAG, "fileChanged(%s,%s,%s)", aFile.getName(), aImage, aFirst);
        SharedPreferences.Editor editor = mSettings.edit();
        if (aIsFavourites && !mShowingFavourites) {
            editor.putString(SettingsActivity.PREFS_LASTDIR, mCurrentFile.toString());
            editor.putInt(SettingsActivity.PREFS_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FIRSTIMAGE, mCurrentScrollY);
        } else if (mShowingFavourites) {
            editor.putInt(SettingsActivity.PREFS_FAV_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FAV_FIRSTIMAGE, mCurrentScrollY);
        }
        editor.apply();
        mCurrentFile = aFile;
        mCurrentImage = aImage;
        setStatus(mCurrentFile);
        mCurrentScrollY = aFirst;

        if (mGridView != null) {
            if (mThumbnailAdapter != null) {
                mThumbnailAdapter.cancel();
            }
            mThumbnailAdapter = new ThumbnailAdapter(getActivity(), mCurrentFile, mCurrentImage, mThumbSize,
                    GridViewFragment.this);
            mGridView.clearChoices();
            mGridView.setAdapter(mThumbnailAdapter);
            mGridView.setSelection(mCurrentImage);
            mGridView.setSelection(mCurrentScrollY);
            mGridView.setItemChecked(mCurrentImage, true);
            mThumbnailAdapter.resume();
        }
        if (mPreview != null) {
            mPreview.setPresentation(mThumbnailAdapter.getPresentation(), mCurrentImage);
        }
        if (!aIsFavourites && !mShowingFavourites) {
            editor = mSettings.edit();
            editor.putString(SettingsActivity.PREFS_LASTDIR, mCurrentFile.toString());
            editor.putInt(SettingsActivity.PREFS_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FIRSTIMAGE, mCurrentScrollY);
            editor.commit();
        }
        mShowingFavourites = aIsFavourites;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onFavouritesChanged(File aFile) {
        mFavourites.importFile(aFile);
        mFavourites.save(true);
        final SharedPreferences slideshowSettings = PreferenceManager.getDefaultSharedPreferences(getActivity());
        SharedPreferences.Editor editor = slideshowSettings.edit();
        String name = aFile.getName().substring(0,
                aFile.getName().length() - FavouritesManager.FAVOURITES_EXT.length());
        SlideShowActivity.debug(1, TAG, "updateFavourites: %s", name);
        editor.putString(SettingsActivity.PREFS_FAVOURITES, name);
        editor.apply();
        fileChanged(mFavourites.getRootFile(), 0, 0, true);
    }

    /**
     * Set the title/subtitle in the ActionBar to reflect the current path/sub-path
     * @param aFile The current file location.
     */
    private void setStatus(final ZFile aFile) {
        final ActionBar actionBar = getActivity().getActionBar();
        if (actionBar != null) {
            String subPath = aFile.getSubPath();
            String images = getString(R.string.text_images_placeholder);
            if (subPath.endsWith(images)) {
                subPath = subPath.substring(subPath.lastIndexOf(images));
                if (subPath.endsWith(File.separator)) {
                    subPath = subPath.substring(0, subPath.length() - 1);
                }
            }
            if (aFile.isZipFile()) {
                File zip = new File(aFile.getRootPath());
                actionBar.setTitle(zip.getName());
                if (subPath.length() == 0) {
                    actionBar.setSubtitle(null);
                } else {
                    if (subPath.length() > 30) {
                        subPath = "\u2026" + subPath.substring(subPath.length() - 30);
                    }
                    actionBar.setSubtitle(subPath);
                }
            } else {
                actionBar.setTitle(aFile.getName());
                actionBar.setSubtitle(null);
            }
        }
    }

    /**
     * Called from main activity when the back key is pressed to allow this fragment to perform a suitable action.
     * @return true if the back key event has been consumed (i.e. no further action should be taken).
     */
    public boolean offerBackKeyEvent() {
        if (mShowingFavourites) {
            // close the favourites and return to normal view
            SharedPreferences.Editor editor = mSettings.edit();
            editor.putInt(SettingsActivity.PREFS_FAV_CURRENTIMAGE, mCurrentImage);
            editor.putInt(SettingsActivity.PREFS_FAV_FIRSTIMAGE, mCurrentScrollY);
            editor.apply();
            ZFile currentFile = new ZFile(
                    mSettings.getString(SettingsActivity.PREFS_LASTDIR, mCurrentFile.toString()));
            int currentImage = mSettings.getInt(SettingsActivity.PREFS_CURRENTIMAGE, 0);
            int currentScrollY = mSettings.getInt(SettingsActivity.PREFS_FIRSTIMAGE, 0);
            fileChanged(currentFile, currentImage, currentScrollY, false);
            getActivity().invalidateOptionsMenu();
            return true;
        } else if (mGuardBackKey) {
            // need double back key within 2 seconds to close
            long now = System.currentTimeMillis();
            if (now - mBackTimestampMillis > 2000) {
                Toast.makeText(getActivity(), R.string.text_press_back_again, Toast.LENGTH_SHORT).show();
                mBackTimestampMillis = now;
                return true;
            }
        }
        return false;
    }

    /**
     * Set the grid choice mode depending on the visibility of the preview pane.
     */
    public void setGridChoiceMode() {
        boolean visible = mPreview != null && mPreview.isPreviewVisible();
        mGridView.setChoiceMode(
                (mPreview == null || !visible) ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_SINGLE);
        mGridView.setSelection(mCurrentImage);
        mGridView.setItemChecked(mCurrentImage, true);
    }

    /**
     * {@inheritDoc}
     * @see ThumbnailAdapter.ThumbnailAdapterListener
     */
    @Override
    public void showProgress(int aVisibility) {
        if (mProgressBar != null) {
            mProgressBar.bringToFront();
            mProgressBar.setVisibility(aVisibility);
        }
    }

    /**
     * {@inheritDoc}
     * @see ThumbnailAdapter.ThumbnailAdapterListener
     */
    @Override
    public void onThumbnailsReady() {
        /* do nothing */ }

    /**
     * {@inheritDoc}
     * In this case we select the given slide in the grid view.
     * @see PreviewFragment.PreviewFragmentListener
     */
    @Override
    public void onNewPreviewSlide(int aSlide) {

        SlideShowActivity.debug(2, TAG, "onNewPreviewSlide(%s)", aSlide);
        mCurrentImage = aSlide;
        mGridView.smoothScrollToPosition(mCurrentImage);
        mCurrentScrollY = mGridView.getFirstVisiblePosition();
        mGridView.setItemChecked(mCurrentImage, true);
        mGridView.invalidate();
    }

    /**
     * {@inheritDoc}
      * In this case we select the given slide in the grid view.
     * @see PreviewFragment.PreviewFragmentListener
     */
    @Override
    public void onPreviewStartSlideshow() {
        View previewView = mPreview.getView();
        if (previewView != null) {
            ActivityOptions options = ActivityOptions.makeScaleUpAnimation(mGridView, (int) previewView.getX(),
                    (int) previewView.getY(), previewView.getWidth(), previewView.getHeight());
            startSlideshow(mCurrentFile, mCurrentImage, false, options);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isFlingPermitted() {
        return mActionMode == null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getCurrentImage() {
        return mCurrentImage;
    }

    /**
     * {@inheritDoc}
     */
    private class GridViewMultiChoiceModeListener implements MultiChoiceModeListener {

        /** Flag indicating if all items have been selected. */
        private boolean mSelectAll = false;

        /**
         * {@inheritDoc}
         * Updates preview image to show selected slide.
         */
        @Override
        public void onItemCheckedStateChanged(ActionMode actionMode, int position, long id, boolean checked) {
            if (mPreview != null && !mSelectAll) {
                mPreview.setImage(position);
                mCurrentImage = position;
                mCurrentScrollY = mGridView.getFirstVisiblePosition();
            }
            mSelectAll &= checked;
            actionMode.invalidate();
        }

        /** 
         * {@inheritDoc}
         */
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            mActionMode = mode;
            mode.getMenuInflater().inflate(R.menu.thumbnail_popup_menu, menu);
            UiHider.setActionMode(true);
            switch (mThumbnailAdapter.mPresentation.getType()) {
            case Presentation.LIST_PRESENTATION:
                menu.removeItem(R.id.thumbnailAddToSlideshow);
                break;
            case Presentation.ZIP_PRESENTATION:
            case Presentation.DIR_PRESENTATION:
                break;
            }
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            int count = mGridView.getCheckedItemCount();
            boolean isList = mThumbnailAdapter.getPresentation().getType() == Presentation.LIST_PRESENTATION;
            if (count == 0) {
                menu.setGroupVisible(R.id.thumbnail_popup_single, false);
                menu.setGroupVisible(R.id.thumbnail_popup_add, false);
                menu.setGroupVisible(R.id.thumbnail_popup_remove, false);
                menu.setGroupVisible(R.id.thumbnail_popup_selection, true);
            } else {
                if (isList) {
                    menu.setGroupVisible(R.id.thumbnail_popup_remove, true);
                    menu.setGroupVisible(R.id.thumbnail_popup_add, false);
                } else {
                    menu.setGroupVisible(R.id.thumbnail_popup_remove, false);
                    menu.setGroupVisible(R.id.thumbnail_popup_add, true);
                }
                if (count == 1) {
                    menu.setGroupVisible(R.id.thumbnail_popup_single, true);
                    menu.setGroupVisible(R.id.thumbnail_popup_selection, true);
                } else {
                    menu.setGroupVisible(R.id.thumbnail_popup_single, false);
                    menu.setGroupVisible(R.id.thumbnail_popup_selection, true);
                }
            }
            menu.setGroupVisible(R.id.thumbnail_popup_extract, mCurrentFile.isZipFile());
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean onActionItemClicked(ActionMode actionMode, MenuItem item) {
            SparseBooleanArray selection = mGridView.getCheckedItemPositions();
            int selectedCount = mGridView.getCheckedItemCount();
            switch (item.getItemId()) {
            case R.id.thumbnailSetWallpaper:
                if (selectedCount == 1) {
                    int i = selection.keyAt(0);
                    BitmapManager.setWallpaper(getActivity(), mThumbnailAdapter.getPresentation(),
                            mThumbnailAdapter.getName(i));
                    mThumbnailAdapter.notifyDataSetChanged();
                }
                actionMode.finish();
                return true;
            case R.id.thumbnailDetails:
                if (selectedCount == 1) {
                    BitmapManager.displayBitmapProperties(getActivity(), mThumbnailAdapter.getPresentation(),
                            selection.keyAt(0));
                    selection.clear();
                    mThumbnailAdapter.notifyDataSetChanged();
                }
                actionMode.finish();
                return true;
            case R.id.thumbnailAddToSlideshow: {
                for (int i = 0; i < selection.size(); i++) {
                    if (selection.get(selection.keyAt(i))) {
                        String path = mThumbnailAdapter.getPath(selection.keyAt(i));
                        mFavourites.add(path);
                    }
                }
                Toast.makeText(getActivity(), R.string.text_added_favourite, Toast.LENGTH_SHORT).show();
                selection.clear();
                mThumbnailAdapter.notifyDataSetChanged();
                actionMode.finish();
                return true;
            }
            case R.id.thumbnailRemoveFromSlideshow: {
                for (int i = selection.size() - 1; i >= 0; i--) {
                    int pos = selection.keyAt(i);
                    if (selection.get(pos)) {
                        String path = mThumbnailAdapter.getPath(pos);
                        if (mThumbnailAdapter.mPresentation.getType() == Presentation.LIST_PRESENTATION) {
                            int index = mFavourites.remove(path);
                            SlideShowActivity.debug(1, TAG, "removeFromSlideshow: index=%d, pos=%d", index, pos);
                            if (index >= 0) {
                                mThumbnailAdapter.remove(pos);
                                mCurrentImage = Math.max(0, Math.min(pos, mThumbnailAdapter.getCount() - 1));
                                mCurrentScrollY = mGridView.getFirstVisiblePosition();
                            }
                        }
                    }
                }
                mFavourites.save(false);
                mGridView.setItemChecked(mCurrentImage, true);
                mPreview.setImage(mCurrentImage);
                mGridView.invalidate();
                selection.clear();
                mThumbnailAdapter.notifyDataSetChanged();
                actionMode.finish();
                return true;
            }
            case R.id.thumbnailSelectAll:
                mSelectAll = true;
                for (int i = 0; i < mGridView.getCount(); i++) {
                    mGridView.setItemChecked(i, true);
                }
                mSelectAll = false;
                actionMode.invalidate();
                return true;
            case R.id.thumbnailExtract:
                ArrayList<File> names = new ArrayList<>(mGridView.getCheckedItemCount());
                SparseBooleanArray checked = mGridView.getCheckedItemPositions();
                for (int i = 0; i < mThumbnailAdapter.getCount(); i++) {
                    if (checked.get(i, false)) {
                        names.add(new File(mThumbnailAdapter.getName(i)));
                        SlideShowActivity.debug(1, TAG, "action_extract: name %s", mThumbnailAdapter.getName(i));
                    }
                }
                ZFile source = new ZFile(mCurrentFile.toString());
                if (source.getName().compareTo(getString(R.string.text_images_placeholder)) == 0) {
                    source = new ZFile(source.getParentPath());
                }
                File path = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                        getActivity().getString(R.string.app_name));
                File dest = new File(path, source.getName());
                SlideShowActivity.debug(1, TAG, "action_extract: dest %s", dest.toString());
                if (!dest.exists()) {
                    if (!dest.mkdirs()) {
                        Toast.makeText(getActivity(), R.string.text_save_failed, Toast.LENGTH_SHORT).show();
                        return false;
                    }
                }
                ZipPresentation zp = (ZipPresentation) (mThumbnailAdapter.getPresentation());
                int ok = zp.extract(names, dest);
                String message = String.format(getActivity().getString(R.string.text_extracted), ok,
                        ok == 1 ? "s" : "", dest);
                Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
                actionMode.finish();
                return true;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onDestroyActionMode(ActionMode p1) {
            mGridView.clearChoices();
            UiHider.setActionMode(false);
            // need to defer the setting of choice mode because doing it here ends up with recursive calls to finish()
            mGridView.getHandler().post(new Runnable() {
                @Override
                public void run() {
                    SlideShowActivity.debug(1, TAG, "mChoiceModeRunnable.run() %s %s", mCurrentImage,
                            mCurrentScrollY);
                    setGridChoiceMode();
                    if (mPreview != null) {
                        mPreview.setImage(mCurrentImage);
                    }
                    mThumbnailAdapter.notifyDataSetChanged();
                }
            });
            mActionMode = null;
        }

    }

}