public final class

GridLayer

extends RootLayer
implements MediaFeed.Listener TimeBar.Listener
/*
 * Copyright (C) 2009 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.cooliris.media;

import java.util.ArrayList;
import javax.microedition.khronos.opengles.GL11;

import android.hardware.SensorEvent;
import android.opengl.GLU;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.content.Context;

import com.cooliris.app.App;
import com.cooliris.app.Res;

public final class GridLayer extends RootLayer implements MediaFeed.Listener, TimeBar.Listener {
    private static final String TAG = "GridLayer";
    public static final int STATE_MEDIA_SETS = 0;
    public static final int STATE_GRID_VIEW = 1;
    public static final int STATE_FULL_SCREEN = 2;
    public static final int STATE_TIMELINE = 3;

    public static final int ANCHOR_LEFT = 0;
    public static final int ANCHOR_RIGHT = 1;
    public static final int ANCHOR_CENTER = 2;

    public static final int MAX_ITEMS_PER_SLOT = 32;
    public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
    public static final int MAX_DISPLAYED_ITEMS_PER_FOCUSED_SLOT = 32;
    public static final int MAX_DISPLAY_SLOTS = 96;
    public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;

    private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;

    private HudLayer mHud;
    private int mState;
    private final IndexRange mBufferedVisibleRange = new IndexRange();
    private final IndexRange mVisibleRange = new IndexRange();
    private final IndexRange mPreviousDataRange = new IndexRange();
    private final IndexRange mCompleteRange = new IndexRange();

    private final Pool<Vector3f> mTempVec;
    private final Pool<Vector3f> mTempVecAlt;

    private final ArrayList<MediaItem> mTempList = new ArrayList<MediaItem>();
    private final MediaItem[] mTempHash = new MediaItem[64];

    private final Vector3f mDeltaAnchorPositionUncommited = new Vector3f();
    private Vector3f mDeltaAnchorPosition = new Vector3f();

    // The display primitives.
    final private GridDrawables mDrawables;
    private float mSelectedAlpha = 0.0f;
    private float mTargetAlpha = 0.0f;

    final private GridCamera mCamera;
    final private GridCameraManager mCameraManager;
    final private GridDrawManager mDrawManager;
    final private GridInputProcessor mInputProcessor;

    private boolean mFeedAboutToChange;
    private boolean mPerformingLayoutChange;
    private boolean mFeedChanged;

    private final LayoutInterface mLayoutInterface;
    private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);

    private MediaFeed mMediaFeed;
    private boolean mInAlbum = false;
    private int mCurrentExpandedSlot;

    private final DisplayList mDisplayList = new DisplayList();
    private final DisplayItem[] mDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
    private final DisplaySlot[] mDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
    private ArrayList<MediaItem> mVisibleItems;

    private final BackgroundLayer mBackground;
    private boolean mLocationFilter;
    private float mZoomValue = 1.0f;
    private float mCurrentFocusItemWidth = 1.0f;
    private float mCurrentFocusItemHeight = 1.0f;
    private float mTimeElapsedSinceGridViewReady = 0.0f;

    private boolean mSlideshowMode;
    private boolean mNoDeleteMode = false;
    private float mTimeElapsedSinceView;
    private final MediaBucketList mSelectedBucketList = new MediaBucketList();
    private final MediaBucketList mMarkedBucketList = new MediaBucketList();
    private float mTimeElapsedSinceStackViewReady;

    private Context mContext;
    private RenderView mView;
    private boolean mPickIntent;
    private boolean mViewIntent;
    private WakeLock mWakeLock;
    private int mStartMemoryRange;
    private int mFramesDirty;
    private String mRequestFocusContentUri;
    private int mFrameCount;
    private boolean mRequestToEnterSelection;

    // private ArrayList<Integer> mBreakSlots = new ArrayList<Integer>();
    // private ArrayList<Integer> mOldBreakSlots;
    // private LongSparseArray<Integer> mBreakSlots = new
    // LongSparseArray<Integer>();
    // private LongSparseArray<Integer> mOldBreakSlots;

    public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
        mBackground = new BackgroundLayer(this);
        mContext = context;
        mView = view;

        Vector3f[] vectorPool = new Vector3f[128];
        int length = vectorPool.length;
        for (int i = 0; i < length; ++i) {
            vectorPool[i] = new Vector3f();
        }
        Vector3f[] vectorPoolRenderThread = new Vector3f[128];
        length = vectorPoolRenderThread.length;
        for (int i = 0; i < length; ++i) {
            vectorPoolRenderThread[i] = new Vector3f();
        }
        mTempVec = new Pool<Vector3f>(vectorPool);
        mTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);

        DisplaySlot[] displaySlots = mDisplaySlots;
        for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
            DisplaySlot slot = new DisplaySlot();
            displaySlots[i] = slot;
        }
        mLayoutInterface = layoutInterface;
        mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
        mDrawables = new GridDrawables(itemWidth, itemHeight);
        mBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
        mVisibleRange.set(Shared.INVALID, Shared.INVALID);
        mCompleteRange.set(Shared.INVALID, Shared.INVALID);
        mPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
        mDeltaAnchorPosition.set(0, 0, 0);
        mDeltaAnchorPositionUncommited.set(0, 0, 0);
        mSelectedBucketList.clear();

        mVisibleItems = new ArrayList<MediaItem>();
        mHud = new HudLayer(context);
        mHud.setContext(context);
        mHud.setGridLayer(this);
        mHud.getPathBar().clear();
        mHud.setGridLayer(this);
        mHud.getTimeBar().setListener(this);
        mHud.getPathBar().pushLabel(Res.drawable.icon_home_small, context.getResources().getString(Res.string.app_name),
                new Runnable() {
                    public void run() {
                        if (mHud.getAlpha() == 1.0f) {
                            if (!mFeedAboutToChange) {
                                setState(STATE_MEDIA_SETS);
                            }
                        } else {
                            mHud.setAlpha(1.0f);
                        }
                    }
                });
        mCameraManager = new GridCameraManager(mCamera);
        mDrawManager = new GridDrawManager(context, mCamera, mDrawables, mDisplayList, mDisplayItems, mDisplaySlots);
        mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, mTempVec, mDisplayItems);
        setState(STATE_MEDIA_SETS);
    }

    public HudLayer getHud() {
        return mHud;
    }

    public void shutdown() {
        if (mMediaFeed != null) {
            mMediaFeed.shutdown();
        }
        mContext = null;
        mSelectedBucketList.clear();
        mView = null;
    }

    public void stop() {
        endSlideshow();
        mBackground.clear();
        handleLowMemory();
    }

    @Override
    public void generate(RenderView view, RenderView.Lists lists) {
        lists.updateList.add(this);
        lists.opaqueList.add(this);
        mBackground.generate(view, lists);
        lists.blendedList.add(this);
        lists.hitTestList.add(this);
        mHud.generate(view, lists);
    }

    @Override
    protected void onSizeChanged() {
        mHud.setSize(mWidth, mHeight);
        mHud.setAlpha(1.0f);
        mBackground.setSize(mWidth, mHeight);
        if (mView != null) {
            mView.requestRender();
        }
    }

    public int getState() {
        return mState;
    }

    public void setState(int state) {
        boolean feedUnchanged = false;
        mCamera.mFriction = 0.0f;
        if (mState == state) {
            feedUnchanged = true;
        }
        GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
        GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
        oldLayout.mNumRows = layoutInterface.mNumRows;
        oldLayout.mSpacingX = layoutInterface.mSpacingX;
        oldLayout.mSpacingY = layoutInterface.mSpacingY;
        GridCamera camera = mCamera;
        int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
        MediaFeed feed = mMediaFeed;
        boolean performLayout = true;
        mZoomValue = 1.0f;
        float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
        if (yStretch < 1.0f) {
            yStretch = 1.0f;
        }
        switch (state) {
        case STATE_GRID_VIEW:
            mTimeElapsedSinceGridViewReady = 0.0f;
            if (feed != null && feedUnchanged == false) {
                boolean updatedData = feed.restorePreviousClusteringState();
                if (updatedData) {
                    performLayout = false;
                }
            }
            layoutInterface.mNumRows = numMaxRows;
            layoutInterface.mSpacingX = (int) (10 * App.PIXEL_DENSITY);
            layoutInterface.mSpacingY = (int) (10 * App.PIXEL_DENSITY);
            if (mState == STATE_MEDIA_SETS) {
                // Entering album.
                mInAlbum = true;
                MediaSet set = feed.getCurrentSet();
                int icon = mDrawables.getIconForSet(set, true);
                if (set != null) {
                    mHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
                        public void run() {
                            if (mFeedAboutToChange) {
                                return;
                            }
                            if (mHud.getAlpha() == 1.0f) {
                                disableLocationFiltering();
                                mInputProcessor.clearSelection();
                                setState(STATE_GRID_VIEW);
                            } else {
                                mHud.setAlpha(1.0f);
                            }
                        }
                    });
                }
            }
            if (mState == STATE_FULL_SCREEN) {
                mHud.getPathBar().popLabel();
            }
            break;
        case STATE_TIMELINE:
            mTimeElapsedSinceStackViewReady = 0.0f;
            if (feed != null && feedUnchanged == false) {
                feed.performClustering();
                performLayout = false;
            }
            disableLocationFiltering();
            layoutInterface.mNumRows = numMaxRows - 1;
            layoutInterface.mSpacingX = (int) (100 * App.PIXEL_DENSITY);
            layoutInterface.mSpacingY = (int) (70 * App.PIXEL_DENSITY * yStretch);
            break;
        case STATE_FULL_SCREEN:
            layoutInterface.mNumRows = 1;
            layoutInterface.mSpacingX = (int) (40 * App.PIXEL_DENSITY);
            layoutInterface.mSpacingY = (int) (40 * App.PIXEL_DENSITY);
            if (mState != STATE_FULL_SCREEN) {
                mHud.getPathBar().pushLabel(Res.drawable.ic_fs_details, "", new Runnable() {
                    public void run() {
                        if (mHud.getAlpha() == 1.0f) {
                            mHud.swapFullscreenLabel();
                        }
                        mHud.setAlpha(1.0f);
                    }
                });
            }
            break;
        case STATE_MEDIA_SETS:
            mTimeElapsedSinceStackViewReady = 0.0f;
            if (feed != null && feedUnchanged == false) {
                feed.restorePreviousClusteringState();
                mMarkedBucketList.clear();
                feed.expandMediaSet(Shared.INVALID);
                performLayout = false;
            }
            disableLocationFiltering();
            mInputProcessor.clearSelection();
            layoutInterface.mNumRows = numMaxRows - 1;
            layoutInterface.mSpacingX = (int) (100 * App.PIXEL_DENSITY);
            layoutInterface.mSpacingY = (int) (70 * App.PIXEL_DENSITY * yStretch);
            if (mInAlbum) {
                if (mState == STATE_FULL_SCREEN) {
                    mHud.getPathBar().popLabel();
                }
                mHud.getPathBar().popLabel();
                mInAlbum = false;
            }
            break;
        }
        mState = state;
        mHud.onGridStateChanged();
        if (performLayout && mFeedAboutToChange == false) {
            onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
        }
        if (state != STATE_FULL_SCREEN) {
            mCamera.moveYTo(0);
            mCamera.moveZTo(0);
        }
    }

    protected void enableLocationFiltering(String label) {
        if (mLocationFilter == false) {
            mLocationFilter = true;
            mHud.getPathBar().pushLabel(Res.drawable.icon_location_small, label, new Runnable() {
                public void run() {
                    if (mHud.getAlpha() == 1.0f) {
                        if (mState == STATE_FULL_SCREEN) {
                            mInputProcessor.clearSelection();
                            setState(STATE_GRID_VIEW);
                        } else {
                            disableLocationFiltering();
                        }
                    } else {
                        mHud.setAlpha(1.0f);
                    }
                }
            });
        }
    }

    protected void disableLocationFiltering() {
        if (mLocationFilter) {
            mLocationFilter = false;
            mMediaFeed.removeFilter();
            mHud.getPathBar().popLabel();
        }
    }

    boolean goBack() {
        if (mFeedAboutToChange) {
            return false;
        }
        int state = mState;
        if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
            if (mLocationFilter) {
                disableLocationFiltering();
                setState(STATE_TIMELINE);
                return true;
            }
        }
        switch (state) {
        case STATE_GRID_VIEW:
            setState(STATE_MEDIA_SETS);
            break;
        case STATE_TIMELINE:
            setState(STATE_GRID_VIEW);
            break;
        case STATE_FULL_SCREEN:
            setState(STATE_GRID_VIEW);
            mInputProcessor.clearSelection();
            break;
        default:
            return false;
        }
        return true;
    }

    public void endSlideshow() {
        mSlideshowMode = false;
        if (mWakeLock != null) {
            if (mWakeLock.isHeld()) {
                mWakeLock.release();
            }
            mWakeLock = null;
        }
        mHud.setAlpha(1.0f);
    }

    @Override
    public void onSensorChanged(RenderView view, SensorEvent event) {
        mInputProcessor.onSensorChanged(view, event, mState);
    }

    public DataSource getDataSource() {
        if (mMediaFeed != null)
            return mMediaFeed.getDataSource();
        return null;
    }

    public void setDataSource(DataSource dataSource) {
        MediaFeed feed = mMediaFeed;
        mMediaFeed = new MediaFeed(mContext, dataSource, this);
        if (feed != null) {
            // Restore the slot state in the original feed before shutting it down.
            mMediaFeed.copySlotStateFrom(feed);

            feed.shutdown();
            mDisplayList.clear();
            mBackground.clear();
        }

        mMediaFeed.start();
    }

    public IndexRange getVisibleRange() {
        return mVisibleRange;
    }

    public IndexRange getBufferedVisibleRange() {
        return mBufferedVisibleRange;
    }

    public IndexRange getCompleteRange() {
        return mCompleteRange;
    }

    private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
        int retVal = Shared.INVALID;
        int firstSlotIndex = 0;
        int lastSlotIndex = 0;
        IndexRange rangeToUse = mVisibleRange;
        synchronized (rangeToUse) {
            firstSlotIndex = rangeToUse.begin;
            lastSlotIndex = rangeToUse.end;
        }
        Pool<Vector3f> pool = mTempVec;
        float itemWidthBy2 = itemWidth * 0.5f;
        float itemHeightBy2 = itemHeight * 0.5f;
        Vector3f position = pool.create();
        Vector3f deltaAnchorPosition = pool.create();
        try {
            deltaAnchorPosition.set(mDeltaAnchorPosition);
            for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
                GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
                if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
                        position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
                    retVal = i;
                    break;
                }
            }
        } finally {
            pool.delete(deltaAnchorPosition);
            pool.delete(position);
        }
        return retVal;
    }

    void centerCameraForSlot(int slotIndex, float baseConvergence) {
        float imageTheta = 0.0f;
        DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
        if (displayItem != null) {
            imageTheta = displayItem.getImageTheta();
        }
        mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, mDeltaAnchorPositionUncommited,
                mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
    }

    boolean constrainCameraForSlot(int slotIndex) {
        return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, mDeltaAnchorPosition, mCurrentFocusItemWidth,
                mCurrentFocusItemHeight);
    }

    // Called on render thread before rendering.
    @Override
    public boolean update(RenderView view, float timeElapsed) {
        if (mFeedAboutToChange == false) {
            mTimeElapsedSinceGridViewReady += timeElapsed;
            if (mTimeElapsedSinceGridViewReady >= 1.0f) {
                mTimeElapsedSinceGridViewReady = 1.0f;
            }
            mTimeElapsedSinceStackViewReady += timeElapsed;
            if (mTimeElapsedSinceStackViewReady >= 1.0f) {
                mTimeElapsedSinceStackViewReady = 1.0f;
            }
        }
        if (mRequestToEnterSelection) {
            HudLayer hud = getHud();
            if (hud != null) {
                hud.enterSelectionMode();
                if (hud.getMode() == HudLayer.MODE_SELECT) {
                  mRequestToEnterSelection = false;
                  addSlotToSelectedItems(mInputProcessor.getCurrentSelectedSlot(), true, true);
                }
            }
        }

        // isSingleImageMode() returns true only when the item is not found in DB.
        // In that case, we won't enter into the selection mode.
        if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
            HudLayer hud = getHud();
            hud.getPathBar().setHidden(true);
            hud.getMenuBar().setHidden(true);
            if (hud.getMode() != HudLayer.MODE_NORMAL)
                hud.setMode(HudLayer.MODE_NORMAL);
        }
        if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
            mHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
        } else {
            mHud.getPathBar().setAnimatedIcons(null);
        }

        // In that case, we need to commit the respective Display Items when the
        // feed was updated.
        GridCamera camera = mCamera;
        camera.update(timeElapsed);
        DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
        if (anchorDisplayItem != null && !mHud.getTimeBar().isDragged()) {
            mHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
        }
        mDisplayList.update(timeElapsed);
        mInputProcessor.update(timeElapsed);
        mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
        if (mState == STATE_FULL_SCREEN) {
            mHud.autoHide(true);
        } else {
            mHud.autoHide(false);
            mHud.setAlpha(1.0f);
        }
        GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
        int numFullScreenQuads = fullscreenQuads.length;
        for (int i = 0; i < numFullScreenQuads; ++i) {
            fullscreenQuads[i].update(timeElapsed);
        }
        if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
            mTimeElapsedSinceView += timeElapsed;
            if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
                // time to go to the next slide
                mTimeElapsedSinceView = 0.0f;
                changeFocusToNextSlot(0.5f);
                mCamera.commitMoveInX();
                mCamera.commitMoveInY();
            }
        }
        if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
            mCamera.moveYTo(-0.1f);
            mCamera.commitMoveInY();
        }
        boolean dirty = mDrawManager.update(timeElapsed);
        dirty |= mSlideshowMode;
        dirty |= mFramesDirty > 0;
        ++mFrameCount;
        if (mFramesDirty > 0) {
            --mFramesDirty;
        }
        if (mDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
                || mSelectedAlpha != mTargetAlpha
                // || (mAnimatedFov != mTargetFov)
                || dirty)
            return true;
        else
            return false;
    }

    private void computeVisibleRange() {
        if (mPerformingLayoutChange)
            return;
        if (mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited) == false) {
            mDeltaAnchorPosition.set(mDeltaAnchorPositionUncommited);
        }
        mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, mDeltaAnchorPosition, mVisibleRange,
                mBufferedVisibleRange, mCompleteRange, mState);
    }

    private void computeVisibleItems() {
        if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
            return;
        }
        computeVisibleRange();
        int deltaBegin = mBufferedVisibleRange.begin - mPreviousDataRange.begin;
        int deltaEnd = mBufferedVisibleRange.end - mPreviousDataRange.end;
        if (deltaBegin != 0 || deltaEnd != 0) {
            // The delta has changed, we have to compute the display items
            // again.
            // We find the intersection range, these slots have not changed at
            // all.
            int firstVisibleSlotIndex = mBufferedVisibleRange.begin;
            int lastVisibleSlotIndex = mBufferedVisibleRange.end;
            mPreviousDataRange.begin = firstVisibleSlotIndex;
            mPreviousDataRange.end = lastVisibleSlotIndex;

            Pool<Vector3f> pool = mTempVec;
            Vector3f position = pool.create();
            Vector3f deltaAnchorPosition = pool.create();
            try {
                MediaFeed feed = mMediaFeed;
                DisplayList displayList = mDisplayList;
                DisplayItem[] displayItems = mDisplayItems;
                DisplaySlot[] displaySlots = mDisplaySlots;
                int numDisplayItems = displayItems.length;
                int numDisplaySlots = displaySlots.length;
                ArrayList<MediaItem> visibleItems = mVisibleItems;
                deltaAnchorPosition.set(mDeltaAnchorPosition);
                LayoutInterface layout = mLayoutInterface;
                GridCamera camera = mCamera;
                for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
                    GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
                    MediaSet set = feed.getSetForSlot(i);
                    int indexIntoSlots = i - firstVisibleSlotIndex;

                    if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
                        ArrayList<MediaItem> items = set.getItems();
                        displaySlots[indexIntoSlots].setMediaSet(set);
                        ArrayList<MediaItem> bestItems = mTempList;
                        {
                            // We always show the same top thumbnails for a
                            // stack of albums
                            // if (mState == STATE_MEDIA_SETS)
                            // ArrayUtils.computeSortedIntersection(items,
                            // visibleItems, MAX_ITEMS_PER_SLOT, bestItems,
                            // sTempHash);
                            // else
                            ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, mTempHash);
                        }

                        int numItemsInSet = set.getNumItems();
                        int numBestItems = bestItems.size();
                        int originallyFoundItems = numBestItems;
                        if (numBestItems < MAX_ITEMS_PER_SLOT) {
                            int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
                            for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
                                MediaItem item = items.get(currItemPos);
                                if (!bestItems.contains(item)) {
                                    bestItems.add(item);
                                    if (--itemsRemaining == 0) {
                                        break;
                                    }
                                }
                            }
                        }
                        numBestItems = bestItems.size();
                        int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
                        for (int j = 0; j < numBestItems; ++j) {
                            if (baseIndex + j >= numDisplayItems) {
                                break;
                            }
                            if (j >= numItemsInSet) {
                                displayItems[baseIndex + j] = null;
                            } else {
                                MediaItem item = bestItems.get(j);
                                if (item != null) {
                                    DisplayItem displayItem = displayList.get(item);
                                    if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
                                            || (mState == STATE_GRID_VIEW && j >= originallyFoundItems)) {
                                        displayItem.set(position, j, false);
                                        displayItem.commit();
                                    } else {
                                        displayList.setPositionAndStackIndex(displayItem, position, j, true);
                                    }
                                    displayItems[baseIndex + j] = displayItem;
                                }
                            }
                        }
                        for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
                            displayItems[baseIndex + j] = null;
                        }
                        bestItems.clear();
                    }
                }
                if (mFeedChanged) {
                    mFeedChanged = false;
                    if (mInputProcessor != null && mState == STATE_FULL_SCREEN && mRequestFocusContentUri == null) {
                        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
                        if (currentSelectedSlot > mCompleteRange.end)
                            currentSelectedSlot = mCompleteRange.end;
                        mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
                    }
                    if (mState == STATE_GRID_VIEW) {
                        MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
                        if (expandedSet != null) {
                            if (mHud != null) {
                                final PathBarLayer pathBar = mHud.getPathBar();
                                if (pathBar != null) {
                                    final String currentLabel = pathBar.getCurrentLabel();
                                    if (currentLabel == null || !currentLabel.equals(expandedSet.mNoCountTitleString)) {
                                        pathBar.changeLabel(expandedSet.mNoCountTitleString);
                                    }
                                }
                            }
                        }
                    }
                    if (mRequestFocusContentUri != null) {
                        // We have to find the item that has this contentUri
                        int numSlots = mCompleteRange.end + 1;
                        for (int i = 0; i < numSlots; ++i) {
                            MediaSet set = feed.getSetForSlot(i);
                            ArrayList<MediaItem> items = set.getItems();
                            int numItems = items.size();
                            for (int j = 0; j < numItems; ++j) {
                                String itemUri = items.get(j).mContentUri;
                                if (itemUri != null && mRequestFocusContentUri != null) {
                                    if (itemUri.equals(mRequestFocusContentUri)) {
                                        if (mState == STATE_FULL_SCREEN) {
                                            mInputProcessor.setCurrentSelectedSlot(i);
                                        } else {
                                            centerCameraForSlot(i, 1.0f);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                        mRequestFocusContentUri = null;
                    }
                }
            } finally {
                pool.delete(position);
                pool.delete(deltaAnchorPosition);
            }
            // We keep upto 400 thumbnails in memory.
            int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
            int startMemoryRange = (mBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
            if (mStartMemoryRange != startMemoryRange) {
                mStartMemoryRange = startMemoryRange;
                clearUnusedThumbnails();
            }
        }
    }

    @Override
    public void handleLowMemory() {
        clearUnusedThumbnails();
        GridDrawables.sStringTextureTable.clear();
        mBackground.clearCache();
    }

    // This method can be potentially expensive
    public void clearUnusedThumbnails() {
        mDisplayList.clearExcept(mDisplayItems);
    }

    @Override
    public void onSurfaceCreated(RenderView view, GL11 gl) {
        mDisplayList.clear();
        mHud.clear();
        mHud.reset();
        GridDrawables.sStringTextureTable.clear();
        mDrawables.onSurfaceCreated(view, gl);
        mBackground.clear();
    }

    @Override
    public void onSurfaceChanged(RenderView view, int width, int height) {
        mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
        view.setFov(mCamera.mFov);
        setState(mState);
    }

    // Renders the node in a given pass.
    public void renderOpaque(RenderView view, GL11 gl) {
        GridCamera camera = mCamera;
        int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
        computeVisibleItems();

        gl.glMatrixMode(GL11.GL_MODELVIEW);
        gl.glLoadIdentity();
        GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
                camera.mUpX, camera.mUpY, camera.mUpZ);
        view.setAlpha(1.0f);
        if (mSelectedAlpha != 1.0f) {
            gl.glEnable(GL11.GL_BLEND);
            gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
            view.setAlpha(mSelectedAlpha);
        }
        if (selectedSlotIndex != Shared.INVALID) {
            mTargetAlpha = 0.0f;
        } else {
            mTargetAlpha = 1.0f;
        }
        mDrawManager.prepareDraw(mBufferedVisibleRange, mVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
                mInputProcessor.getCurrentScaledSlot(), mInputProcessor.isFocusItemPressed(), mInputProcessor.getScale(),
                mInputProcessor.getScaleGestureDetector(), mFeedAboutToChange);
        if (mSelectedAlpha != 0.0f) {
            mDrawManager.drawThumbnails(view, gl, mState);
        }
        if (mSelectedAlpha != 1.0f) {
            gl.glDisable(GL11.GL_BLEND);
        }
        // We draw the selected slotIndex.
        if (selectedSlotIndex != Shared.INVALID) {
            mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
            mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
            mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
        }
        view.setAlpha(mSelectedAlpha);
    }

    public void renderBlended(RenderView view, GL11 gl) {
        // We draw the placeholder for all visible slots.
        if (mHud != null && mDrawManager != null) {
            if (mMediaFeed != null) {
                mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(),
                        mTimeElapsedSinceStackViewReady, mTimeElapsedSinceGridViewReady, mSelectedBucketList, mMarkedBucketList,
                        mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange || mMediaFeed.isLoading());
            }
        }
    }

    public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
        if (mPerformingLayoutChange || !mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited)) {
            return;
        }
        // mOldBreakSlots = mBreakSlots;
        if (mState == STATE_GRID_VIEW) {
            final ArrayList<Integer> breaks = mMediaFeed.getBreaks();
        } else {
            // mBreakSlots = null;
        }
        mPerformingLayoutChange = true;
        LayoutInterface layout = mLayoutInterface;
        if (oldLayout == null) {
            oldLayout = sfullScreenLayoutInterface;
        }
        GridCamera camera = mCamera;
        if (currentAnchorSlotIndex == Shared.INVALID) {
            currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
            if (mCurrentExpandedSlot != Shared.INVALID) {
                currentAnchorSlotIndex = mCurrentExpandedSlot;
            }
            int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
            if (selectedSlotIndex != Shared.INVALID) {
                currentAnchorSlotIndex = selectedSlotIndex;
            }
        }
        if (newAnchorSlotIndex == Shared.INVALID) {
            newAnchorSlotIndex = currentAnchorSlotIndex;
        }
        int itemHeight = camera.mItemHeight;
        int itemWidth = camera.mItemWidth;
        Pool<Vector3f> pool = mTempVec;
        Vector3f deltaAnchorPosition = pool.create();
        Vector3f currentSlotPosition = pool.create();
        try {
            deltaAnchorPosition.set(0, 0, 0);
            if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
                layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
                oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
                currentSlotPosition.subtract(mDeltaAnchorPosition);
                deltaAnchorPosition.subtract(currentSlotPosition);
                deltaAnchorPosition.y = 0;
                deltaAnchorPosition.z = 0;
            }
            mDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
        } finally {
            pool.delete(deltaAnchorPosition);
            pool.delete(currentSlotPosition);
        }
        centerCameraForSlot(newAnchorSlotIndex, 1.0f);
        mCurrentExpandedSlot = Shared.INVALID;

        // Force recompute of visible items and their positions.
        ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
        ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
        ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
        forceRecomputeVisibleRange();
        mPerformingLayoutChange = false;
    }

    private void forceRecomputeVisibleRange() {
        mPreviousDataRange.begin = Shared.INVALID;
        mPreviousDataRange.end = Shared.INVALID;
        if (mView != null) {
            mView.requestRender();
        }
    }

    // called on background thread
    public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
        if (!needsLayout && !mFeedAboutToChange) {
            mFeedChanged = true;
            forceRecomputeVisibleRange();
            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
                mHud.setFeed(feed, mState, needsLayout);
            return;
        }

        while (mPerformingLayoutChange == true) {
            Thread.yield();
        }
        if (mState == STATE_GRID_VIEW) {
            if (mHud != null) {
                MediaSet set = feed.getCurrentSet();
                if (set != null && !mLocationFilter)
                    mHud.getPathBar().changeLabel(set.mNoCountTitleString);
            }
        }
        DisplayItem[] displayItems = mDisplayItems;
        int firstBufferedVisibleSlotIndex = mBufferedVisibleRange.begin;
        int lastBufferedVisibleSlotIndex = mBufferedVisibleRange.end;
        int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
        int numVisibleItems = mVisibleRange.end - mVisibleRange.begin + 1;
        if (mState == STATE_MEDIA_SETS && currentlyVisibleSlotIndex < numVisibleItems) {
            currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_LEFT);
        }
        if (mCurrentExpandedSlot != Shared.INVALID) {
            currentlyVisibleSlotIndex = mCurrentExpandedSlot;
        }
        MediaItem anchorItem = null;
        ArrayList<MediaItem> visibleItems = mVisibleItems;
        visibleItems.clear();
        visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
        if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
                && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
            int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
            for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
                DisplayItem displayItem = displayItems[baseIndex + i];
                if (displayItem != null) {
                    if (anchorItem == null) {
                        anchorItem = displayItem.mItemRef;
                    }
                    visibleItems.add(displayItem.mItemRef);
                }
            }
        }
        // We want to add items from the middle.
        int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
        int midPoint = currentlyVisibleSlotIndex;
        int length = displayItems.length;
        for (int i = 0; i < numItems; ++i) {
            int index = midPoint + Shared.midPointIterator(i);
            int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
            if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
                for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
                    DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
                    if (displayItem != null) {
                        MediaItem item = displayItem.mItemRef;
                        if (!visibleItems.contains(item)) {
                            visibleItems.add(item);
                        }
                    }
                }
            }
        }
        int newSlotIndex = Shared.INVALID;
        if (anchorItem != null) {
            // We try to find the anchor item in the new feed.
            int numSlots = feed.getNumSlots();
            for (int i = 0; i < numSlots; ++i) {
                MediaSet set = feed.getSetForSlot(i);
                if (set != null && ArrayUtils.contains(set.getItems(), anchorItem)) {
                    newSlotIndex = i;
                    break;
                }
            }
        }

        if (anchorItem != null && newSlotIndex == Shared.INVALID) {
            int numSlots = feed.getNumSlots();
            MediaSet parentSet = anchorItem.mParentMediaSet;
            for (int i = 0; i < numSlots; ++i) {
                MediaSet set = feed.getSetForSlot(i);
                if (set != null && set.mId == parentSet.mId) {
                    newSlotIndex = i;
                    break;
                }
            }
        }
        // We must create a new display store now since the data has changed.
        Log.i(TAG, "Slot changing from " + currentlyVisibleSlotIndex + " to " + newSlotIndex);
        if (newSlotIndex != Shared.INVALID) {
            if (mState == STATE_MEDIA_SETS) {
                mDisplayList.clearExcept(displayItems);
            }
            onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
        } else {
            forceRecomputeVisibleRange();
        }
        mCurrentExpandedSlot = Shared.INVALID;
        mFeedAboutToChange = false;
        mFeedChanged = true;
        if (feed != null) {
            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
                mHud.setFeed(feed, mState, needsLayout);
        }
        if (mView != null) {
            mView.requestRender();
        }
    }

    public DisplayItem getRepresentativeDisplayItem() {
        int slotIndex = Shared.INVALID;
        if (mInputProcessor != null) {
            slotIndex = mInputProcessor.getCurrentFocusSlot();
        }
        if (slotIndex == Shared.INVALID) {
            slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
        }
        int index = (slotIndex - mBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
        if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
            return mDisplayItems[index];
        } else {
            return null;
        }
    }

    public DisplayItem getAnchorDisplayItem(int type) {
        int slotIndex = getAnchorSlotIndex(type);
        int index = (slotIndex - mBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
        if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
            return mDisplayItems[index];
        } else {
            return null;
        }
    }

    public float getScrollPosition() {
        return (mCamera.mLookAtX * mCamera.mScale + mDeltaAnchorPosition.x); // in
        // pixels
    }

    public DisplayItem getDisplayItemForScrollPosition(float posX) {
        Pool<Vector3f> pool = mTempVecAlt;
        MediaFeed feed = mMediaFeed;
        int itemWidth = mCamera.mItemWidth;
        int itemHeight = mCamera.mItemHeight;
        GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
        float absolutePosX = posX;
        int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
        int right = feed == null ? 0 : (int) (feed.getNumSlots());
        int retSlot = left;
        Vector3f position = pool.create();
        try {
            for (int i = left; i < right; ++i) {
                gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
                retSlot = i;
                if (position.x >= absolutePosX) {
                    break;
                }
            }
        } finally {
            pool.delete(position);
        }
        if (mFeedAboutToChange) {
            return null;
        }
        right = feed == null ? 0 : feed.getNumSlots();
        if (right == 0) {
            return null;
        }

        if (retSlot >= right)
            retSlot = right - 1;
        MediaSet set = feed.getSetForSlot(retSlot);
        if (set != null) {
            ArrayList<MediaItem> items = set.getItems();
            if (items != null && set.getNumItems() > 0) {
                return (mDisplayList.get(items.get(0)));
            }
        }
        return null;
    }

    // Returns the top left-most item.
    public int getAnchorSlotIndex(int anchorType) {
        int retVal = 0;
        switch (anchorType) {
        case ANCHOR_LEFT:
            retVal = mVisibleRange.begin;
            break;
        case ANCHOR_RIGHT:
            retVal = mVisibleRange.end;
            break;
        case ANCHOR_CENTER:
            retVal = (mVisibleRange.begin + mVisibleRange.end) / 2;
            break;
        }
        return retVal;
    }

    DisplayItem getDisplayItemForSlotId(int slotId) {
        int index = slotId - mBufferedVisibleRange.begin;
        if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
            return mDisplayItems[index * MAX_ITEMS_PER_SLOT];
        }
        return null;
    }

    boolean changeFocusToNextSlot(float convergence) {
        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
        boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
        if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
            endSlideshow();
            mHud.setAlpha(1.0f);
        }
        return retVal;
    }

    boolean changeFocusToSlot(int slotId, float convergence) {
        mZoomValue = 1.0f;
        int index = slotId - mBufferedVisibleRange.begin;
        if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
            DisplayItem displayItem = mDisplayItems[index * MAX_ITEMS_PER_SLOT];
            if (displayItem != null) {
                MediaItem item = displayItem.mItemRef;
                mHud.fullscreenSelectionChanged(item, slotId + 1, mCompleteRange.end + 1);
                if (slotId != Shared.INVALID && slotId <= mCompleteRange.end) {
                    mInputProcessor.setCurrentFocusSlot(slotId);
                    centerCameraForSlot(slotId, convergence);
                    return true;
                } else {
                    centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
                    return false;
                }
            }
        }
        return false;
    }

    boolean changeFocusToPreviousSlot(float convergence) {
        return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
    }

    public ArrayList<MediaBucket> getSelectedBuckets() {
        return mSelectedBucketList.get();
    }

    public void selectAll() {
        if (mState != STATE_FULL_SCREEN) {
            int numSlots = mCompleteRange.end + 1;
            for (int i = 0; i < numSlots; ++i) {
                addSlotToSelectedItems(i, false, false);
            }
            updateCountOfSelectedItems();
        } else {
            addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
        }
    }

    public void deselectOrCancelSelectMode() {
        if (mSelectedBucketList.size() == 0) {
            mHud.cancelSelection();
        } else {
            mSelectedBucketList.clear();
            updateCountOfSelectedItems();
        }
    }

    public void deselectAll() {
        mHud.cancelSelection();
        mSelectedBucketList.clear();
        updateCountOfSelectedItems();
    }

    public void deleteSelection() {
        // Delete the selection and exit selection mode.
        mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
        deselectAll();

        // If the current set is now empty, return to the parent set.
        if (mCompleteRange.isEmpty()) {
            goBack(); // TODO(venkat): This does not work most of the time, can
            // you take a look?
        }
    }

    void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
        // mMediaFeed may be null because setDataSource() may not be called yet.
        if (mFeedAboutToChange == false && mMediaFeed != null) {
            MediaFeed feed = mMediaFeed;
            mSelectedBucketList.add(slotId, feed, removeIfAlreadyAdded);
            if (updateCount) {
                updateCountOfSelectedItems();
                if (mSelectedBucketList.size() == 0)
                    deselectAll();
            }
        }
        mHud.computeBottomMenu();
    }

    private void updateCountOfSelectedItems() {
        mHud.updateNumItemsSelected(mSelectedBucketList.size());
    }

    public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * App.PIXEL_DENSITY), mCamera.mItemHeight
                + (int) (100 * App.PIXEL_DENSITY));
    }

    public int getSlotIndexForScreenPosition(int posX, int posY) {
        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
    }

    private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
        Pool<Vector3f> pool = mTempVec;
        int retVal = 0;
        Vector3f worldPos = pool.create();
        try {
            GridCamera camera = mCamera;
            camera.convertToCameraSpace(posX, posY, 0, worldPos);
            // slots are expressed in pixels as well
            worldPos.x *= camera.mScale;
            worldPos.y *= camera.mScale;
            // we ignore z
            retVal = hitTest(worldPos, itemWidth, itemHeight);
        } finally {
            pool.delete(worldPos);
        }
        return retVal;
    }

    public boolean tapGesture(int slotIndex, boolean metadata) {
        MediaFeed feed = mMediaFeed;
        if (feed == null)
            return false;
        if (!feed.isClustered()) {
            // It is not clustering.
            if (!feed.hasExpandedMediaSet()) {
                if (feed.canExpandSet(slotIndex)) {
                    mCurrentExpandedSlot = slotIndex;
                    feed.expandMediaSet(slotIndex);
                    setState(STATE_GRID_VIEW);
                }
                return false;
            } else {
                return true;
            }
        } else {
            // Select a cluster, and recompute a new cluster within this
            // cluster.
            mCurrentExpandedSlot = slotIndex;
            mMarkedBucketList.clear();
            mMarkedBucketList.add(slotIndex, feed, false);
            goBack();
            if (metadata) {
                DisplaySlot slot = mDisplaySlots[slotIndex - mBufferedVisibleRange.begin];
                if (slot.hasValidLocation()) {
                    MediaSet set = slot.getMediaSet();
                    if (set.mReverseGeocodedLocation != null) {
                        enableLocationFiltering(set.mReverseGeocodedLocation);
                    }
                    feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude,
                            set.mMaxLonLongitude));
                }
            }
            return false;
        }
    }

    public void onTimeChanged(TimeBar timebar) {
        if (mFeedAboutToChange) {
            return;
        }
        // TODO lot of optimization possible here
        MediaItem item = timebar.getItem();
        if (item == null)
           return;
        MediaFeed feed = mMediaFeed;
        if (feed == null)
            return;
        int numSlots = feed.getNumSlots();
        for (int i = 0; i < numSlots; ++i) {
            MediaSet set = feed.getSetForSlot(i);
            if (set == null) {
                return;
            }
            ArrayList<MediaItem> items = set.getItems();
            if (items == null || set.getNumItems() == 0) {
                return;
            }
            if (ArrayUtils.contains(items, item)) {
                centerCameraForSlot(i, 1.0f);
                break;
            }
        }
    }

    public void onFeedAboutToChange(MediaFeed feed) {
        mFeedAboutToChange = true;
    }

    public void startSlideshow() {
        endSlideshow();
        mSlideshowMode = true;
        mZoomValue = 1.0f;
        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
        mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
        mHud.setAlpha(0);
        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
        mWakeLock.acquire();
    }

    public void enterSelectionMode() {
        mSlideshowMode = false;
        mHud.enterSelectionMode();
        int currentSlot = mInputProcessor.getCurrentSelectedSlot();
        if (currentSlot == Shared.INVALID) {
            currentSlot = mInputProcessor.getCurrentFocusSlot();
        }
        addSlotToSelectedItems(currentSlot, false, true);
    }

    private float getFillScreenZoomValue() {
        return GridCameraManager.getFillScreenZoomValue(mCamera, mTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
    }

    public void zoomInToSelectedItem() {
        mSlideshowMode = false;
        float potentialZoomValue = getFillScreenZoomValue();
        if (mZoomValue < potentialZoomValue) {
            mZoomValue = potentialZoomValue;
        } else {
            mZoomValue *= 3.0f;
        }
        if (mZoomValue > 6.0f) {
            mZoomValue = 6.0f;
        }
        mHud.setAlpha(1.0f);
        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
    }

    public void zoomOutFromSelectedItem() {
        mSlideshowMode = false;
        if (mZoomValue == getFillScreenZoomValue()) {
            mZoomValue = 1.0f;
        } else {
            mZoomValue /= 3.0f;
        }
        if (mZoomValue < 1.0f) {
            mZoomValue = 1.0f;
        }
        mHud.setAlpha(1.0f);
        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
    }

    public void rotateSelectedItems(float f) {
        MediaBucketList bucketList = mSelectedBucketList;
        ArrayList<MediaBucket> mediaBuckets = bucketList.get();
        DisplayList displayList = mDisplayList;
        int numBuckets = mediaBuckets.size();
        for (int i = 0; i < numBuckets; ++i) {
            MediaBucket bucket = mediaBuckets.get(i);
            ArrayList<MediaItem> mediaItems = bucket.mediaItems;
            if (mediaItems != null) {
                int numMediaItems = mediaItems.size();
                for (int j = 0; j < numMediaItems; ++j) {
                    MediaItem item = mediaItems.get(j);
                    DisplayItem displayItem = displayList.get(item);
                    displayItem.rotateImageBy(f);
                    displayList.addToAnimatables(displayItem);
                }
            }
        }
        if (mState == STATE_FULL_SCREEN) {
            centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
        }
        mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
        // we recreate these displayitems from the cache
    }

    public void cropSelectedItem() {

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mInputProcessor.onTouchEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (mInputProcessor != null)
            return mInputProcessor.onKeyDown(keyCode, event, mState);
        return false;
    }

    public boolean inSlideShowMode() {
        return mSlideshowMode;
    }

    public boolean noDeleteMode() {
        return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
    }

    public float getZoomValue() {
        return mZoomValue;
    }

    public boolean feedAboutToChange() {
        return mFeedAboutToChange;
    }

    public boolean isInAlbumMode() {
        return mInAlbum;
    }

    public Vector3f getDeltaAnchorPosition() {
        return mDeltaAnchorPosition;
    }

    public int getExpandedSlot() {
        return mCurrentExpandedSlot;
    }

    public GridLayoutInterface getLayoutInterface() {
        return (GridLayoutInterface) mLayoutInterface;
    }

    public void setZoomValue(float f) {
        mZoomValue = f;
        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 10.0f);
    }

    public void setPickIntent(boolean b) {
        mPickIntent = b;
        mHud.getPathBar().popLabel();
        mHud.getPathBar().pushLabel(Res.drawable.icon_location_small, mContext.getResources().getString(Res.string.pick),
                new Runnable() {
                    public void run() {
                        if (mHud.getAlpha() == 1.0f) {
                            if (!mFeedAboutToChange) {
                                setState(STATE_MEDIA_SETS);
                            }
                        } else {
                            mHud.setAlpha(1.0f);
                        }
                    }
                });
    }

    public boolean getPickIntent() {
        return mPickIntent;
    }

    public void setViewIntent(boolean b, final String setName) {
        mViewIntent = b;
        if (b) {
            mMediaFeed.expandMediaSet(0);
            setState(STATE_GRID_VIEW);
            // We need to make sure we haven't pushed the same label twice
            if (mHud.getPathBar().getNumLevels() == 1) {
                mHud.getPathBar().pushLabel(Res.drawable.icon_folder_small, setName, new Runnable() {
                    public void run() {
                        if (mFeedAboutToChange) {
                            return;
                        }
                        if (mHud.getAlpha() == 1.0f) {
                            disableLocationFiltering();
                            if (mInputProcessor != null)
                                mInputProcessor.clearSelection();
                            setState(STATE_GRID_VIEW);
                        } else {
                            mHud.setAlpha(1.0f);
                        }
                    }
                });
            }
        }
    }

    public boolean getViewIntent() {
        return mViewIntent;
    }

    public void setSingleImage(boolean noDeleteMode) {
        mNoDeleteMode = noDeleteMode;
        mInputProcessor.setCurrentSelectedSlot(0);
    }

    public MediaFeed getFeed() {
        return mMediaFeed;
    }

    public void markDirty(int numFrames) {
        mFramesDirty = numFrames;
    }

    public void focusItem(String contentUri) {
        mRequestFocusContentUri = contentUri;
        if (mMediaFeed != null) {
            mMediaFeed.updateListener(false);
        }
    }

    public void onResume() {
        if (mMediaFeed != null) {
            mMediaFeed.onResume();
        }
    }

    public void onPause() {
        if (mMediaFeed != null) {
            mMediaFeed.onPause();
        }
    }

    public void setEnterSelectionMode(boolean enterSelection) {
        mRequestToEnterSelection = enterSelection;
    }
}