1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.cooliris.media;
18
19import java.util.ArrayList;
20import javax.microedition.khronos.opengles.GL11;
21
22import android.hardware.SensorEvent;
23import android.opengl.GLU;
24import android.os.PowerManager;
25import android.os.PowerManager.WakeLock;
26import android.util.Log;
27import android.view.KeyEvent;
28import android.view.MotionEvent;
29import android.content.Context;
30
31import com.cooliris.app.App;
32import com.cooliris.app.Res;
33
34public final class GridLayer extends RootLayer implements MediaFeed.Listener, TimeBar.Listener {
35    private static final String TAG = "GridLayer";
36    public static final int STATE_MEDIA_SETS = 0;
37    public static final int STATE_GRID_VIEW = 1;
38    public static final int STATE_FULL_SCREEN = 2;
39    public static final int STATE_TIMELINE = 3;
40
41    public static final int ANCHOR_LEFT = 0;
42    public static final int ANCHOR_RIGHT = 1;
43    public static final int ANCHOR_CENTER = 2;
44
45    public static final int MAX_ITEMS_PER_SLOT = 32;
46    public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
47    public static final int MAX_DISPLAYED_ITEMS_PER_FOCUSED_SLOT = 32;
48    public static final int MAX_DISPLAY_SLOTS = 96;
49    public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;
50
51    private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
52
53    private HudLayer mHud;
54    private int mState;
55    private final IndexRange mBufferedVisibleRange = new IndexRange();
56    private final IndexRange mVisibleRange = new IndexRange();
57    private final IndexRange mPreviousDataRange = new IndexRange();
58    private final IndexRange mCompleteRange = new IndexRange();
59
60    private final Pool<Vector3f> mTempVec;
61    private final Pool<Vector3f> mTempVecAlt;
62
63    private final ArrayList<MediaItem> mTempList = new ArrayList<MediaItem>();
64    private final MediaItem[] mTempHash = new MediaItem[64];
65
66    private final Vector3f mDeltaAnchorPositionUncommited = new Vector3f();
67    private Vector3f mDeltaAnchorPosition = new Vector3f();
68
69    // The display primitives.
70    final private GridDrawables mDrawables;
71    private float mSelectedAlpha = 0.0f;
72    private float mTargetAlpha = 0.0f;
73
74    final private GridCamera mCamera;
75    final private GridCameraManager mCameraManager;
76    final private GridDrawManager mDrawManager;
77    final private GridInputProcessor mInputProcessor;
78
79    private boolean mFeedAboutToChange;
80    private boolean mPerformingLayoutChange;
81    private boolean mFeedChanged;
82
83    private final LayoutInterface mLayoutInterface;
84    private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);
85
86    private MediaFeed mMediaFeed;
87    private boolean mInAlbum = false;
88    private int mCurrentExpandedSlot;
89
90    private final DisplayList mDisplayList = new DisplayList();
91    private final DisplayItem[] mDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
92    private final DisplaySlot[] mDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
93    private ArrayList<MediaItem> mVisibleItems;
94
95    private final BackgroundLayer mBackground;
96    private boolean mLocationFilter;
97    private float mZoomValue = 1.0f;
98    private float mCurrentFocusItemWidth = 1.0f;
99    private float mCurrentFocusItemHeight = 1.0f;
100    private float mTimeElapsedSinceGridViewReady = 0.0f;
101
102    private boolean mSlideshowMode;
103    private boolean mNoDeleteMode = false;
104    private float mTimeElapsedSinceView;
105    private final MediaBucketList mSelectedBucketList = new MediaBucketList();
106    private final MediaBucketList mMarkedBucketList = new MediaBucketList();
107    private float mTimeElapsedSinceStackViewReady;
108
109    private Context mContext;
110    private RenderView mView;
111    private boolean mPickIntent;
112    private boolean mViewIntent;
113    private WakeLock mWakeLock;
114    private int mStartMemoryRange;
115    private int mFramesDirty;
116    private String mRequestFocusContentUri;
117    private int mFrameCount;
118    private boolean mRequestToEnterSelection;
119
120    // private ArrayList<Integer> mBreakSlots = new ArrayList<Integer>();
121    // private ArrayList<Integer> mOldBreakSlots;
122    // private LongSparseArray<Integer> mBreakSlots = new
123    // LongSparseArray<Integer>();
124    // private LongSparseArray<Integer> mOldBreakSlots;
125
126    public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
127        mBackground = new BackgroundLayer(this);
128        mContext = context;
129        mView = view;
130
131        Vector3f[] vectorPool = new Vector3f[128];
132        int length = vectorPool.length;
133        for (int i = 0; i < length; ++i) {
134            vectorPool[i] = new Vector3f();
135        }
136        Vector3f[] vectorPoolRenderThread = new Vector3f[128];
137        length = vectorPoolRenderThread.length;
138        for (int i = 0; i < length; ++i) {
139            vectorPoolRenderThread[i] = new Vector3f();
140        }
141        mTempVec = new Pool<Vector3f>(vectorPool);
142        mTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
143
144        DisplaySlot[] displaySlots = mDisplaySlots;
145        for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
146            DisplaySlot slot = new DisplaySlot();
147            displaySlots[i] = slot;
148        }
149        mLayoutInterface = layoutInterface;
150        mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
151        mDrawables = new GridDrawables(itemWidth, itemHeight);
152        mBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
153        mVisibleRange.set(Shared.INVALID, Shared.INVALID);
154        mCompleteRange.set(Shared.INVALID, Shared.INVALID);
155        mPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
156        mDeltaAnchorPosition.set(0, 0, 0);
157        mDeltaAnchorPositionUncommited.set(0, 0, 0);
158        mSelectedBucketList.clear();
159
160        mVisibleItems = new ArrayList<MediaItem>();
161        mHud = new HudLayer(context);
162        mHud.setContext(context);
163        mHud.setGridLayer(this);
164        mHud.getPathBar().clear();
165        mHud.setGridLayer(this);
166        mHud.getTimeBar().setListener(this);
167        mHud.getPathBar().pushLabel(Res.drawable.icon_home_small, context.getResources().getString(Res.string.app_name),
168                new Runnable() {
169                    public void run() {
170                        if (mHud.getAlpha() == 1.0f) {
171                            if (!mFeedAboutToChange) {
172                                setState(STATE_MEDIA_SETS);
173                            }
174                        } else {
175                            mHud.setAlpha(1.0f);
176                        }
177                    }
178                });
179        mCameraManager = new GridCameraManager(mCamera);
180        mDrawManager = new GridDrawManager(context, mCamera, mDrawables, mDisplayList, mDisplayItems, mDisplaySlots);
181        mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, mTempVec, mDisplayItems);
182        setState(STATE_MEDIA_SETS);
183    }
184
185    public HudLayer getHud() {
186        return mHud;
187    }
188
189    public void shutdown() {
190        if (mMediaFeed != null) {
191            mMediaFeed.shutdown();
192        }
193        mContext = null;
194        mSelectedBucketList.clear();
195        mView = null;
196    }
197
198    public void stop() {
199        endSlideshow();
200        mBackground.clear();
201        handleLowMemory();
202    }
203
204    @Override
205    public void generate(RenderView view, RenderView.Lists lists) {
206        lists.updateList.add(this);
207        lists.opaqueList.add(this);
208        mBackground.generate(view, lists);
209        lists.blendedList.add(this);
210        lists.hitTestList.add(this);
211        mHud.generate(view, lists);
212    }
213
214    @Override
215    protected void onSizeChanged() {
216        mHud.setSize(mWidth, mHeight);
217        mHud.setAlpha(1.0f);
218        mBackground.setSize(mWidth, mHeight);
219        if (mView != null) {
220            mView.requestRender();
221        }
222    }
223
224    public int getState() {
225        return mState;
226    }
227
228    public void setState(int state) {
229        boolean feedUnchanged = false;
230        mCamera.mFriction = 0.0f;
231        if (mState == state) {
232            feedUnchanged = true;
233        }
234        GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
235        GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
236        oldLayout.mNumRows = layoutInterface.mNumRows;
237        oldLayout.mSpacingX = layoutInterface.mSpacingX;
238        oldLayout.mSpacingY = layoutInterface.mSpacingY;
239        GridCamera camera = mCamera;
240        int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
241        MediaFeed feed = mMediaFeed;
242        boolean performLayout = true;
243        mZoomValue = 1.0f;
244        float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
245        if (yStretch < 1.0f) {
246            yStretch = 1.0f;
247        }
248        switch (state) {
249        case STATE_GRID_VIEW:
250            mTimeElapsedSinceGridViewReady = 0.0f;
251            if (feed != null && feedUnchanged == false) {
252                boolean updatedData = feed.restorePreviousClusteringState();
253                if (updatedData) {
254                    performLayout = false;
255                }
256            }
257            layoutInterface.mNumRows = numMaxRows;
258            layoutInterface.mSpacingX = (int) (10 * App.PIXEL_DENSITY);
259            layoutInterface.mSpacingY = (int) (10 * App.PIXEL_DENSITY);
260            if (mState == STATE_MEDIA_SETS) {
261                // Entering album.
262                mInAlbum = true;
263                MediaSet set = feed.getCurrentSet();
264                int icon = mDrawables.getIconForSet(set, true);
265                if (set != null) {
266                    mHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
267                        public void run() {
268                            if (mFeedAboutToChange) {
269                                return;
270                            }
271                            if (mHud.getAlpha() == 1.0f) {
272                                disableLocationFiltering();
273                                mInputProcessor.clearSelection();
274                                setState(STATE_GRID_VIEW);
275                            } else {
276                                mHud.setAlpha(1.0f);
277                            }
278                        }
279                    });
280                }
281            }
282            if (mState == STATE_FULL_SCREEN) {
283                mHud.getPathBar().popLabel();
284            }
285            break;
286        case STATE_TIMELINE:
287            mTimeElapsedSinceStackViewReady = 0.0f;
288            if (feed != null && feedUnchanged == false) {
289                feed.performClustering();
290                performLayout = false;
291            }
292            disableLocationFiltering();
293            layoutInterface.mNumRows = numMaxRows - 1;
294            layoutInterface.mSpacingX = (int) (100 * App.PIXEL_DENSITY);
295            layoutInterface.mSpacingY = (int) (70 * App.PIXEL_DENSITY * yStretch);
296            break;
297        case STATE_FULL_SCREEN:
298            layoutInterface.mNumRows = 1;
299            layoutInterface.mSpacingX = (int) (40 * App.PIXEL_DENSITY);
300            layoutInterface.mSpacingY = (int) (40 * App.PIXEL_DENSITY);
301            if (mState != STATE_FULL_SCREEN) {
302                mHud.getPathBar().pushLabel(Res.drawable.ic_fs_details, "", new Runnable() {
303                    public void run() {
304                        if (mHud.getAlpha() == 1.0f) {
305                            mHud.swapFullscreenLabel();
306                        }
307                        mHud.setAlpha(1.0f);
308                    }
309                });
310            }
311            break;
312        case STATE_MEDIA_SETS:
313            mTimeElapsedSinceStackViewReady = 0.0f;
314            if (feed != null && feedUnchanged == false) {
315                feed.restorePreviousClusteringState();
316                mMarkedBucketList.clear();
317                feed.expandMediaSet(Shared.INVALID);
318                performLayout = false;
319            }
320            disableLocationFiltering();
321            mInputProcessor.clearSelection();
322            layoutInterface.mNumRows = numMaxRows - 1;
323            layoutInterface.mSpacingX = (int) (100 * App.PIXEL_DENSITY);
324            layoutInterface.mSpacingY = (int) (70 * App.PIXEL_DENSITY * yStretch);
325            if (mInAlbum) {
326                if (mState == STATE_FULL_SCREEN) {
327                    mHud.getPathBar().popLabel();
328                }
329                mHud.getPathBar().popLabel();
330                mInAlbum = false;
331            }
332            break;
333        }
334        mState = state;
335        mHud.onGridStateChanged();
336        if (performLayout && mFeedAboutToChange == false) {
337            onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
338        }
339        if (state != STATE_FULL_SCREEN) {
340            mCamera.moveYTo(0);
341            mCamera.moveZTo(0);
342        }
343    }
344
345    protected void enableLocationFiltering(String label) {
346        if (mLocationFilter == false) {
347            mLocationFilter = true;
348            mHud.getPathBar().pushLabel(Res.drawable.icon_location_small, label, new Runnable() {
349                public void run() {
350                    if (mHud.getAlpha() == 1.0f) {
351                        if (mState == STATE_FULL_SCREEN) {
352                            mInputProcessor.clearSelection();
353                            setState(STATE_GRID_VIEW);
354                        } else {
355                            disableLocationFiltering();
356                        }
357                    } else {
358                        mHud.setAlpha(1.0f);
359                    }
360                }
361            });
362        }
363    }
364
365    protected void disableLocationFiltering() {
366        if (mLocationFilter) {
367            mLocationFilter = false;
368            mMediaFeed.removeFilter();
369            mHud.getPathBar().popLabel();
370        }
371    }
372
373    boolean goBack() {
374        if (mFeedAboutToChange) {
375            return false;
376        }
377        int state = mState;
378        if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
379            if (mLocationFilter) {
380                disableLocationFiltering();
381                setState(STATE_TIMELINE);
382                return true;
383            }
384        }
385        switch (state) {
386        case STATE_GRID_VIEW:
387            setState(STATE_MEDIA_SETS);
388            break;
389        case STATE_TIMELINE:
390            setState(STATE_GRID_VIEW);
391            break;
392        case STATE_FULL_SCREEN:
393            setState(STATE_GRID_VIEW);
394            mInputProcessor.clearSelection();
395            break;
396        default:
397            return false;
398        }
399        return true;
400    }
401
402    public void endSlideshow() {
403        mSlideshowMode = false;
404        if (mWakeLock != null) {
405            if (mWakeLock.isHeld()) {
406                mWakeLock.release();
407            }
408            mWakeLock = null;
409        }
410        mHud.setAlpha(1.0f);
411    }
412
413    @Override
414    public void onSensorChanged(RenderView view, SensorEvent event) {
415        mInputProcessor.onSensorChanged(view, event, mState);
416    }
417
418    public DataSource getDataSource() {
419        if (mMediaFeed != null)
420            return mMediaFeed.getDataSource();
421        return null;
422    }
423
424    public void setDataSource(DataSource dataSource) {
425        MediaFeed feed = mMediaFeed;
426        mMediaFeed = new MediaFeed(mContext, dataSource, this);
427        if (feed != null) {
428            // Restore the slot state in the original feed before shutting it down.
429            mMediaFeed.copySlotStateFrom(feed);
430
431            feed.shutdown();
432            mDisplayList.clear();
433            mBackground.clear();
434        }
435
436        mMediaFeed.start();
437    }
438
439    public IndexRange getVisibleRange() {
440        return mVisibleRange;
441    }
442
443    public IndexRange getBufferedVisibleRange() {
444        return mBufferedVisibleRange;
445    }
446
447    public IndexRange getCompleteRange() {
448        return mCompleteRange;
449    }
450
451    private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
452        int retVal = Shared.INVALID;
453        int firstSlotIndex = 0;
454        int lastSlotIndex = 0;
455        IndexRange rangeToUse = mVisibleRange;
456        synchronized (rangeToUse) {
457            firstSlotIndex = rangeToUse.begin;
458            lastSlotIndex = rangeToUse.end;
459        }
460        Pool<Vector3f> pool = mTempVec;
461        float itemWidthBy2 = itemWidth * 0.5f;
462        float itemHeightBy2 = itemHeight * 0.5f;
463        Vector3f position = pool.create();
464        Vector3f deltaAnchorPosition = pool.create();
465        try {
466            deltaAnchorPosition.set(mDeltaAnchorPosition);
467            for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
468                GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
469                if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
470                        position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
471                    retVal = i;
472                    break;
473                }
474            }
475        } finally {
476            pool.delete(deltaAnchorPosition);
477            pool.delete(position);
478        }
479        return retVal;
480    }
481
482    void centerCameraForSlot(int slotIndex, float baseConvergence) {
483        float imageTheta = 0.0f;
484        DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
485        if (displayItem != null) {
486            imageTheta = displayItem.getImageTheta();
487        }
488        mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, mDeltaAnchorPositionUncommited,
489                mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
490    }
491
492    boolean constrainCameraForSlot(int slotIndex) {
493        return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, mDeltaAnchorPosition, mCurrentFocusItemWidth,
494                mCurrentFocusItemHeight);
495    }
496
497    // Called on render thread before rendering.
498    @Override
499    public boolean update(RenderView view, float timeElapsed) {
500        if (mFeedAboutToChange == false) {
501            mTimeElapsedSinceGridViewReady += timeElapsed;
502            if (mTimeElapsedSinceGridViewReady >= 1.0f) {
503                mTimeElapsedSinceGridViewReady = 1.0f;
504            }
505            mTimeElapsedSinceStackViewReady += timeElapsed;
506            if (mTimeElapsedSinceStackViewReady >= 1.0f) {
507                mTimeElapsedSinceStackViewReady = 1.0f;
508            }
509        }
510        if (mRequestToEnterSelection) {
511            HudLayer hud = getHud();
512            if (hud != null) {
513                hud.enterSelectionMode();
514                if (hud.getMode() == HudLayer.MODE_SELECT) {
515                  mRequestToEnterSelection = false;
516                  addSlotToSelectedItems(mInputProcessor.getCurrentSelectedSlot(), true, true);
517                }
518            }
519        }
520
521        // isSingleImageMode() returns true only when the item is not found in DB.
522        // In that case, we won't enter into the selection mode.
523        if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
524            HudLayer hud = getHud();
525            hud.getPathBar().setHidden(true);
526            hud.getMenuBar().setHidden(true);
527            if (hud.getMode() != HudLayer.MODE_NORMAL)
528                hud.setMode(HudLayer.MODE_NORMAL);
529        }
530        if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
531            mHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
532        } else {
533            mHud.getPathBar().setAnimatedIcons(null);
534        }
535
536        // In that case, we need to commit the respective Display Items when the
537        // feed was updated.
538        GridCamera camera = mCamera;
539        camera.update(timeElapsed);
540        DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
541        if (anchorDisplayItem != null && !mHud.getTimeBar().isDragged()) {
542            mHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
543        }
544        mDisplayList.update(timeElapsed);
545        mInputProcessor.update(timeElapsed);
546        mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
547        if (mState == STATE_FULL_SCREEN) {
548            mHud.autoHide(true);
549        } else {
550            mHud.autoHide(false);
551            mHud.setAlpha(1.0f);
552        }
553        GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
554        int numFullScreenQuads = fullscreenQuads.length;
555        for (int i = 0; i < numFullScreenQuads; ++i) {
556            fullscreenQuads[i].update(timeElapsed);
557        }
558        if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
559            mTimeElapsedSinceView += timeElapsed;
560            if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
561                // time to go to the next slide
562                mTimeElapsedSinceView = 0.0f;
563                changeFocusToNextSlot(0.5f);
564                mCamera.commitMoveInX();
565                mCamera.commitMoveInY();
566            }
567        }
568        if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
569            mCamera.moveYTo(-0.1f);
570            mCamera.commitMoveInY();
571        }
572        boolean dirty = mDrawManager.update(timeElapsed);
573        dirty |= mSlideshowMode;
574        dirty |= mFramesDirty > 0;
575        ++mFrameCount;
576        if (mFramesDirty > 0) {
577            --mFramesDirty;
578        }
579        if (mDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
580                || mSelectedAlpha != mTargetAlpha
581                // || (mAnimatedFov != mTargetFov)
582                || dirty)
583            return true;
584        else
585            return false;
586    }
587
588    private void computeVisibleRange() {
589        if (mPerformingLayoutChange)
590            return;
591        if (mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited) == false) {
592            mDeltaAnchorPosition.set(mDeltaAnchorPositionUncommited);
593        }
594        mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, mDeltaAnchorPosition, mVisibleRange,
595                mBufferedVisibleRange, mCompleteRange, mState);
596    }
597
598    private void computeVisibleItems() {
599        if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
600            return;
601        }
602        computeVisibleRange();
603        int deltaBegin = mBufferedVisibleRange.begin - mPreviousDataRange.begin;
604        int deltaEnd = mBufferedVisibleRange.end - mPreviousDataRange.end;
605        if (deltaBegin != 0 || deltaEnd != 0) {
606            // The delta has changed, we have to compute the display items
607            // again.
608            // We find the intersection range, these slots have not changed at
609            // all.
610            int firstVisibleSlotIndex = mBufferedVisibleRange.begin;
611            int lastVisibleSlotIndex = mBufferedVisibleRange.end;
612            mPreviousDataRange.begin = firstVisibleSlotIndex;
613            mPreviousDataRange.end = lastVisibleSlotIndex;
614
615            Pool<Vector3f> pool = mTempVec;
616            Vector3f position = pool.create();
617            Vector3f deltaAnchorPosition = pool.create();
618            try {
619                MediaFeed feed = mMediaFeed;
620                DisplayList displayList = mDisplayList;
621                DisplayItem[] displayItems = mDisplayItems;
622                DisplaySlot[] displaySlots = mDisplaySlots;
623                int numDisplayItems = displayItems.length;
624                int numDisplaySlots = displaySlots.length;
625                ArrayList<MediaItem> visibleItems = mVisibleItems;
626                deltaAnchorPosition.set(mDeltaAnchorPosition);
627                LayoutInterface layout = mLayoutInterface;
628                GridCamera camera = mCamera;
629                for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
630                    GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
631                    MediaSet set = feed.getSetForSlot(i);
632                    int indexIntoSlots = i - firstVisibleSlotIndex;
633
634                    if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
635                        ArrayList<MediaItem> items = set.getItems();
636                        displaySlots[indexIntoSlots].setMediaSet(set);
637                        ArrayList<MediaItem> bestItems = mTempList;
638                        {
639                            // We always show the same top thumbnails for a
640                            // stack of albums
641                            // if (mState == STATE_MEDIA_SETS)
642                            // ArrayUtils.computeSortedIntersection(items,
643                            // visibleItems, MAX_ITEMS_PER_SLOT, bestItems,
644                            // sTempHash);
645                            // else
646                            ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, mTempHash);
647                        }
648
649                        int numItemsInSet = set.getNumItems();
650                        int numBestItems = bestItems.size();
651                        int originallyFoundItems = numBestItems;
652                        if (numBestItems < MAX_ITEMS_PER_SLOT) {
653                            int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
654                            for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
655                                MediaItem item = items.get(currItemPos);
656                                if (!bestItems.contains(item)) {
657                                    bestItems.add(item);
658                                    if (--itemsRemaining == 0) {
659                                        break;
660                                    }
661                                }
662                            }
663                        }
664                        numBestItems = bestItems.size();
665                        int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
666                        for (int j = 0; j < numBestItems; ++j) {
667                            if (baseIndex + j >= numDisplayItems) {
668                                break;
669                            }
670                            if (j >= numItemsInSet) {
671                                displayItems[baseIndex + j] = null;
672                            } else {
673                                MediaItem item = bestItems.get(j);
674                                if (item != null) {
675                                    DisplayItem displayItem = displayList.get(item);
676                                    if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
677                                            || (mState == STATE_GRID_VIEW && j >= originallyFoundItems)) {
678                                        displayItem.set(position, j, false);
679                                        displayItem.commit();
680                                    } else {
681                                        displayList.setPositionAndStackIndex(displayItem, position, j, true);
682                                    }
683                                    displayItems[baseIndex + j] = displayItem;
684                                }
685                            }
686                        }
687                        for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
688                            displayItems[baseIndex + j] = null;
689                        }
690                        bestItems.clear();
691                    }
692                }
693                if (mFeedChanged) {
694                    mFeedChanged = false;
695                    if (mInputProcessor != null && mState == STATE_FULL_SCREEN && mRequestFocusContentUri == null) {
696                        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
697                        if (currentSelectedSlot > mCompleteRange.end)
698                            currentSelectedSlot = mCompleteRange.end;
699                        mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
700                    }
701                    if (mState == STATE_GRID_VIEW) {
702                        MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
703                        if (expandedSet != null) {
704                            if (mHud != null) {
705                                final PathBarLayer pathBar = mHud.getPathBar();
706                                if (pathBar != null) {
707                                    final String currentLabel = pathBar.getCurrentLabel();
708                                    if (currentLabel == null || !currentLabel.equals(expandedSet.mNoCountTitleString)) {
709                                        pathBar.changeLabel(expandedSet.mNoCountTitleString);
710                                    }
711                                }
712                            }
713                        }
714                    }
715                    if (mRequestFocusContentUri != null) {
716                        // We have to find the item that has this contentUri
717                        int numSlots = mCompleteRange.end + 1;
718                        for (int i = 0; i < numSlots; ++i) {
719                            MediaSet set = feed.getSetForSlot(i);
720                            ArrayList<MediaItem> items = set.getItems();
721                            int numItems = items.size();
722                            for (int j = 0; j < numItems; ++j) {
723                                String itemUri = items.get(j).mContentUri;
724                                if (itemUri != null && mRequestFocusContentUri != null) {
725                                    if (itemUri.equals(mRequestFocusContentUri)) {
726                                        if (mState == STATE_FULL_SCREEN) {
727                                            mInputProcessor.setCurrentSelectedSlot(i);
728                                        } else {
729                                            centerCameraForSlot(i, 1.0f);
730                                        }
731                                        break;
732                                    }
733                                }
734                            }
735                        }
736                        mRequestFocusContentUri = null;
737                    }
738                }
739            } finally {
740                pool.delete(position);
741                pool.delete(deltaAnchorPosition);
742            }
743            // We keep upto 400 thumbnails in memory.
744            int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
745            int startMemoryRange = (mBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
746            if (mStartMemoryRange != startMemoryRange) {
747                mStartMemoryRange = startMemoryRange;
748                clearUnusedThumbnails();
749            }
750        }
751    }
752
753    @Override
754    public void handleLowMemory() {
755        clearUnusedThumbnails();
756        GridDrawables.sStringTextureTable.clear();
757        mBackground.clearCache();
758    }
759
760    // This method can be potentially expensive
761    public void clearUnusedThumbnails() {
762        mDisplayList.clearExcept(mDisplayItems);
763    }
764
765    @Override
766    public void onSurfaceCreated(RenderView view, GL11 gl) {
767        mDisplayList.clear();
768        mHud.clear();
769        mHud.reset();
770        GridDrawables.sStringTextureTable.clear();
771        mDrawables.onSurfaceCreated(view, gl);
772        mBackground.clear();
773    }
774
775    @Override
776    public void onSurfaceChanged(RenderView view, int width, int height) {
777        mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
778        view.setFov(mCamera.mFov);
779        setState(mState);
780    }
781
782    // Renders the node in a given pass.
783    public void renderOpaque(RenderView view, GL11 gl) {
784        GridCamera camera = mCamera;
785        int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
786        computeVisibleItems();
787
788        gl.glMatrixMode(GL11.GL_MODELVIEW);
789        gl.glLoadIdentity();
790        GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
791                camera.mUpX, camera.mUpY, camera.mUpZ);
792        view.setAlpha(1.0f);
793        if (mSelectedAlpha != 1.0f) {
794            gl.glEnable(GL11.GL_BLEND);
795            gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
796            view.setAlpha(mSelectedAlpha);
797        }
798        if (selectedSlotIndex != Shared.INVALID) {
799            mTargetAlpha = 0.0f;
800        } else {
801            mTargetAlpha = 1.0f;
802        }
803        mDrawManager.prepareDraw(mBufferedVisibleRange, mVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
804                mInputProcessor.getCurrentScaledSlot(), mInputProcessor.isFocusItemPressed(), mInputProcessor.getScale(),
805                mInputProcessor.getScaleGestureDetector(), mFeedAboutToChange);
806        if (mSelectedAlpha != 0.0f) {
807            mDrawManager.drawThumbnails(view, gl, mState);
808        }
809        if (mSelectedAlpha != 1.0f) {
810            gl.glDisable(GL11.GL_BLEND);
811        }
812        // We draw the selected slotIndex.
813        if (selectedSlotIndex != Shared.INVALID) {
814            mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
815            mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
816            mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
817        }
818        view.setAlpha(mSelectedAlpha);
819    }
820
821    public void renderBlended(RenderView view, GL11 gl) {
822        // We draw the placeholder for all visible slots.
823        if (mHud != null && mDrawManager != null) {
824            if (mMediaFeed != null) {
825                mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(),
826                        mTimeElapsedSinceStackViewReady, mTimeElapsedSinceGridViewReady, mSelectedBucketList, mMarkedBucketList,
827                        mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange || mMediaFeed.isLoading());
828            }
829        }
830    }
831
832    public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
833        if (mPerformingLayoutChange || !mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited)) {
834            return;
835        }
836        // mOldBreakSlots = mBreakSlots;
837        if (mState == STATE_GRID_VIEW) {
838            final ArrayList<Integer> breaks = mMediaFeed.getBreaks();
839        } else {
840            // mBreakSlots = null;
841        }
842        mPerformingLayoutChange = true;
843        LayoutInterface layout = mLayoutInterface;
844        if (oldLayout == null) {
845            oldLayout = sfullScreenLayoutInterface;
846        }
847        GridCamera camera = mCamera;
848        if (currentAnchorSlotIndex == Shared.INVALID) {
849            currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
850            if (mCurrentExpandedSlot != Shared.INVALID) {
851                currentAnchorSlotIndex = mCurrentExpandedSlot;
852            }
853            int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
854            if (selectedSlotIndex != Shared.INVALID) {
855                currentAnchorSlotIndex = selectedSlotIndex;
856            }
857        }
858        if (newAnchorSlotIndex == Shared.INVALID) {
859            newAnchorSlotIndex = currentAnchorSlotIndex;
860        }
861        int itemHeight = camera.mItemHeight;
862        int itemWidth = camera.mItemWidth;
863        Pool<Vector3f> pool = mTempVec;
864        Vector3f deltaAnchorPosition = pool.create();
865        Vector3f currentSlotPosition = pool.create();
866        try {
867            deltaAnchorPosition.set(0, 0, 0);
868            if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
869                layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
870                oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
871                currentSlotPosition.subtract(mDeltaAnchorPosition);
872                deltaAnchorPosition.subtract(currentSlotPosition);
873                deltaAnchorPosition.y = 0;
874                deltaAnchorPosition.z = 0;
875            }
876            mDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
877        } finally {
878            pool.delete(deltaAnchorPosition);
879            pool.delete(currentSlotPosition);
880        }
881        centerCameraForSlot(newAnchorSlotIndex, 1.0f);
882        mCurrentExpandedSlot = Shared.INVALID;
883
884        // Force recompute of visible items and their positions.
885        ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
886        ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
887        ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
888        forceRecomputeVisibleRange();
889        mPerformingLayoutChange = false;
890    }
891
892    private void forceRecomputeVisibleRange() {
893        mPreviousDataRange.begin = Shared.INVALID;
894        mPreviousDataRange.end = Shared.INVALID;
895        if (mView != null) {
896            mView.requestRender();
897        }
898    }
899
900    // called on background thread
901    public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
902        if (!needsLayout && !mFeedAboutToChange) {
903            mFeedChanged = true;
904            forceRecomputeVisibleRange();
905            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
906                mHud.setFeed(feed, mState, needsLayout);
907            return;
908        }
909
910        while (mPerformingLayoutChange == true) {
911            Thread.yield();
912        }
913        if (mState == STATE_GRID_VIEW) {
914            if (mHud != null) {
915                MediaSet set = feed.getCurrentSet();
916                if (set != null && !mLocationFilter)
917                    mHud.getPathBar().changeLabel(set.mNoCountTitleString);
918            }
919        }
920        DisplayItem[] displayItems = mDisplayItems;
921        int firstBufferedVisibleSlotIndex = mBufferedVisibleRange.begin;
922        int lastBufferedVisibleSlotIndex = mBufferedVisibleRange.end;
923        int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
924        int numVisibleItems = mVisibleRange.end - mVisibleRange.begin + 1;
925        if (mState == STATE_MEDIA_SETS && currentlyVisibleSlotIndex < numVisibleItems) {
926            currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_LEFT);
927        }
928        if (mCurrentExpandedSlot != Shared.INVALID) {
929            currentlyVisibleSlotIndex = mCurrentExpandedSlot;
930        }
931        MediaItem anchorItem = null;
932        ArrayList<MediaItem> visibleItems = mVisibleItems;
933        visibleItems.clear();
934        visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
935        if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
936                && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
937            int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
938            for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
939                DisplayItem displayItem = displayItems[baseIndex + i];
940                if (displayItem != null) {
941                    if (anchorItem == null) {
942                        anchorItem = displayItem.mItemRef;
943                    }
944                    visibleItems.add(displayItem.mItemRef);
945                }
946            }
947        }
948        // We want to add items from the middle.
949        int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
950        int midPoint = currentlyVisibleSlotIndex;
951        int length = displayItems.length;
952        for (int i = 0; i < numItems; ++i) {
953            int index = midPoint + Shared.midPointIterator(i);
954            int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
955            if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
956                for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
957                    DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
958                    if (displayItem != null) {
959                        MediaItem item = displayItem.mItemRef;
960                        if (!visibleItems.contains(item)) {
961                            visibleItems.add(item);
962                        }
963                    }
964                }
965            }
966        }
967        int newSlotIndex = Shared.INVALID;
968        if (anchorItem != null) {
969            // We try to find the anchor item in the new feed.
970            int numSlots = feed.getNumSlots();
971            for (int i = 0; i < numSlots; ++i) {
972                MediaSet set = feed.getSetForSlot(i);
973                if (set != null && ArrayUtils.contains(set.getItems(), anchorItem)) {
974                    newSlotIndex = i;
975                    break;
976                }
977            }
978        }
979
980        if (anchorItem != null && newSlotIndex == Shared.INVALID) {
981            int numSlots = feed.getNumSlots();
982            MediaSet parentSet = anchorItem.mParentMediaSet;
983            for (int i = 0; i < numSlots; ++i) {
984                MediaSet set = feed.getSetForSlot(i);
985                if (set != null && set.mId == parentSet.mId) {
986                    newSlotIndex = i;
987                    break;
988                }
989            }
990        }
991        // We must create a new display store now since the data has changed.
992        Log.i(TAG, "Slot changing from " + currentlyVisibleSlotIndex + " to " + newSlotIndex);
993        if (newSlotIndex != Shared.INVALID) {
994            if (mState == STATE_MEDIA_SETS) {
995                mDisplayList.clearExcept(displayItems);
996            }
997            onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
998        } else {
999            forceRecomputeVisibleRange();
1000        }
1001        mCurrentExpandedSlot = Shared.INVALID;
1002        mFeedAboutToChange = false;
1003        mFeedChanged = true;
1004        if (feed != null) {
1005            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
1006                mHud.setFeed(feed, mState, needsLayout);
1007        }
1008        if (mView != null) {
1009            mView.requestRender();
1010        }
1011    }
1012
1013    public DisplayItem getRepresentativeDisplayItem() {
1014        int slotIndex = Shared.INVALID;
1015        if (mInputProcessor != null) {
1016            slotIndex = mInputProcessor.getCurrentFocusSlot();
1017        }
1018        if (slotIndex == Shared.INVALID) {
1019            slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
1020        }
1021        int index = (slotIndex - mBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
1022        if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
1023            return mDisplayItems[index];
1024        } else {
1025            return null;
1026        }
1027    }
1028
1029    public DisplayItem getAnchorDisplayItem(int type) {
1030        int slotIndex = getAnchorSlotIndex(type);
1031        int index = (slotIndex - mBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
1032        if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
1033            return mDisplayItems[index];
1034        } else {
1035            return null;
1036        }
1037    }
1038
1039    public float getScrollPosition() {
1040        return (mCamera.mLookAtX * mCamera.mScale + mDeltaAnchorPosition.x); // in
1041        // pixels
1042    }
1043
1044    public DisplayItem getDisplayItemForScrollPosition(float posX) {
1045        Pool<Vector3f> pool = mTempVecAlt;
1046        MediaFeed feed = mMediaFeed;
1047        int itemWidth = mCamera.mItemWidth;
1048        int itemHeight = mCamera.mItemHeight;
1049        GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
1050        float absolutePosX = posX;
1051        int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
1052        int right = feed == null ? 0 : (int) (feed.getNumSlots());
1053        int retSlot = left;
1054        Vector3f position = pool.create();
1055        try {
1056            for (int i = left; i < right; ++i) {
1057                gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
1058                retSlot = i;
1059                if (position.x >= absolutePosX) {
1060                    break;
1061                }
1062            }
1063        } finally {
1064            pool.delete(position);
1065        }
1066        if (mFeedAboutToChange) {
1067            return null;
1068        }
1069        right = feed == null ? 0 : feed.getNumSlots();
1070        if (right == 0) {
1071            return null;
1072        }
1073
1074        if (retSlot >= right)
1075            retSlot = right - 1;
1076        MediaSet set = feed.getSetForSlot(retSlot);
1077        if (set != null) {
1078            ArrayList<MediaItem> items = set.getItems();
1079            if (items != null && set.getNumItems() > 0) {
1080                return (mDisplayList.get(items.get(0)));
1081            }
1082        }
1083        return null;
1084    }
1085
1086    // Returns the top left-most item.
1087    public int getAnchorSlotIndex(int anchorType) {
1088        int retVal = 0;
1089        switch (anchorType) {
1090        case ANCHOR_LEFT:
1091            retVal = mVisibleRange.begin;
1092            break;
1093        case ANCHOR_RIGHT:
1094            retVal = mVisibleRange.end;
1095            break;
1096        case ANCHOR_CENTER:
1097            retVal = (mVisibleRange.begin + mVisibleRange.end) / 2;
1098            break;
1099        }
1100        return retVal;
1101    }
1102
1103    DisplayItem getDisplayItemForSlotId(int slotId) {
1104        int index = slotId - mBufferedVisibleRange.begin;
1105        if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
1106            return mDisplayItems[index * MAX_ITEMS_PER_SLOT];
1107        }
1108        return null;
1109    }
1110
1111    boolean changeFocusToNextSlot(float convergence) {
1112        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
1113        boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
1114        if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
1115            endSlideshow();
1116            mHud.setAlpha(1.0f);
1117        }
1118        return retVal;
1119    }
1120
1121    boolean changeFocusToSlot(int slotId, float convergence) {
1122        mZoomValue = 1.0f;
1123        int index = slotId - mBufferedVisibleRange.begin;
1124        if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
1125            DisplayItem displayItem = mDisplayItems[index * MAX_ITEMS_PER_SLOT];
1126            if (displayItem != null) {
1127                MediaItem item = displayItem.mItemRef;
1128                mHud.fullscreenSelectionChanged(item, slotId + 1, mCompleteRange.end + 1);
1129                if (slotId != Shared.INVALID && slotId <= mCompleteRange.end) {
1130                    mInputProcessor.setCurrentFocusSlot(slotId);
1131                    centerCameraForSlot(slotId, convergence);
1132                    return true;
1133                } else {
1134                    centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
1135                    return false;
1136                }
1137            }
1138        }
1139        return false;
1140    }
1141
1142    boolean changeFocusToPreviousSlot(float convergence) {
1143        return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
1144    }
1145
1146    public ArrayList<MediaBucket> getSelectedBuckets() {
1147        return mSelectedBucketList.get();
1148    }
1149
1150    public void selectAll() {
1151        if (mState != STATE_FULL_SCREEN) {
1152            int numSlots = mCompleteRange.end + 1;
1153            for (int i = 0; i < numSlots; ++i) {
1154                addSlotToSelectedItems(i, false, false);
1155            }
1156            updateCountOfSelectedItems();
1157        } else {
1158            addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
1159        }
1160    }
1161
1162    public void deselectOrCancelSelectMode() {
1163        if (mSelectedBucketList.size() == 0) {
1164            mHud.cancelSelection();
1165        } else {
1166            mSelectedBucketList.clear();
1167            updateCountOfSelectedItems();
1168        }
1169    }
1170
1171    public void deselectAll() {
1172        mHud.cancelSelection();
1173        mSelectedBucketList.clear();
1174        updateCountOfSelectedItems();
1175    }
1176
1177    public void deleteSelection() {
1178        // Delete the selection and exit selection mode.
1179        mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
1180        deselectAll();
1181
1182        // If the current set is now empty, return to the parent set.
1183        if (mCompleteRange.isEmpty()) {
1184            goBack(); // TODO(venkat): This does not work most of the time, can
1185            // you take a look?
1186        }
1187    }
1188
1189    void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
1190        // mMediaFeed may be null because setDataSource() may not be called yet.
1191        if (mFeedAboutToChange == false && mMediaFeed != null) {
1192            MediaFeed feed = mMediaFeed;
1193            mSelectedBucketList.add(slotId, feed, removeIfAlreadyAdded);
1194            if (updateCount) {
1195                updateCountOfSelectedItems();
1196                if (mSelectedBucketList.size() == 0)
1197                    deselectAll();
1198            }
1199        }
1200        mHud.computeBottomMenu();
1201    }
1202
1203    private void updateCountOfSelectedItems() {
1204        mHud.updateNumItemsSelected(mSelectedBucketList.size());
1205    }
1206
1207    public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
1208        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * App.PIXEL_DENSITY), mCamera.mItemHeight
1209                + (int) (100 * App.PIXEL_DENSITY));
1210    }
1211
1212    public int getSlotIndexForScreenPosition(int posX, int posY) {
1213        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
1214    }
1215
1216    private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
1217        Pool<Vector3f> pool = mTempVec;
1218        int retVal = 0;
1219        Vector3f worldPos = pool.create();
1220        try {
1221            GridCamera camera = mCamera;
1222            camera.convertToCameraSpace(posX, posY, 0, worldPos);
1223            // slots are expressed in pixels as well
1224            worldPos.x *= camera.mScale;
1225            worldPos.y *= camera.mScale;
1226            // we ignore z
1227            retVal = hitTest(worldPos, itemWidth, itemHeight);
1228        } finally {
1229            pool.delete(worldPos);
1230        }
1231        return retVal;
1232    }
1233
1234    public boolean tapGesture(int slotIndex, boolean metadata) {
1235        MediaFeed feed = mMediaFeed;
1236        if (feed == null)
1237            return false;
1238        if (!feed.isClustered()) {
1239            // It is not clustering.
1240            if (!feed.hasExpandedMediaSet()) {
1241                if (feed.canExpandSet(slotIndex)) {
1242                    mCurrentExpandedSlot = slotIndex;
1243                    feed.expandMediaSet(slotIndex);
1244                    setState(STATE_GRID_VIEW);
1245                }
1246                return false;
1247            } else {
1248                return true;
1249            }
1250        } else {
1251            // Select a cluster, and recompute a new cluster within this
1252            // cluster.
1253            mCurrentExpandedSlot = slotIndex;
1254            mMarkedBucketList.clear();
1255            mMarkedBucketList.add(slotIndex, feed, false);
1256            goBack();
1257            if (metadata) {
1258                DisplaySlot slot = mDisplaySlots[slotIndex - mBufferedVisibleRange.begin];
1259                if (slot.hasValidLocation()) {
1260                    MediaSet set = slot.getMediaSet();
1261                    if (set.mReverseGeocodedLocation != null) {
1262                        enableLocationFiltering(set.mReverseGeocodedLocation);
1263                    }
1264                    feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude,
1265                            set.mMaxLonLongitude));
1266                }
1267            }
1268            return false;
1269        }
1270    }
1271
1272    public void onTimeChanged(TimeBar timebar) {
1273        if (mFeedAboutToChange) {
1274            return;
1275        }
1276        // TODO lot of optimization possible here
1277        MediaItem item = timebar.getItem();
1278        if (item == null)
1279           return;
1280        MediaFeed feed = mMediaFeed;
1281        if (feed == null)
1282            return;
1283        int numSlots = feed.getNumSlots();
1284        for (int i = 0; i < numSlots; ++i) {
1285            MediaSet set = feed.getSetForSlot(i);
1286            if (set == null) {
1287                return;
1288            }
1289            ArrayList<MediaItem> items = set.getItems();
1290            if (items == null || set.getNumItems() == 0) {
1291                return;
1292            }
1293            if (ArrayUtils.contains(items, item)) {
1294                centerCameraForSlot(i, 1.0f);
1295                break;
1296            }
1297        }
1298    }
1299
1300    public void onFeedAboutToChange(MediaFeed feed) {
1301        mFeedAboutToChange = true;
1302    }
1303
1304    public void startSlideshow() {
1305        endSlideshow();
1306        mSlideshowMode = true;
1307        mZoomValue = 1.0f;
1308        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1309        mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
1310        mHud.setAlpha(0);
1311        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1312        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
1313        mWakeLock.acquire();
1314    }
1315
1316    public void enterSelectionMode() {
1317        mSlideshowMode = false;
1318        mHud.enterSelectionMode();
1319        int currentSlot = mInputProcessor.getCurrentSelectedSlot();
1320        if (currentSlot == Shared.INVALID) {
1321            currentSlot = mInputProcessor.getCurrentFocusSlot();
1322        }
1323        addSlotToSelectedItems(currentSlot, false, true);
1324    }
1325
1326    private float getFillScreenZoomValue() {
1327        return GridCameraManager.getFillScreenZoomValue(mCamera, mTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
1328    }
1329
1330    public void zoomInToSelectedItem() {
1331        mSlideshowMode = false;
1332        float potentialZoomValue = getFillScreenZoomValue();
1333        if (mZoomValue < potentialZoomValue) {
1334            mZoomValue = potentialZoomValue;
1335        } else {
1336            mZoomValue *= 3.0f;
1337        }
1338        if (mZoomValue > 6.0f) {
1339            mZoomValue = 6.0f;
1340        }
1341        mHud.setAlpha(1.0f);
1342        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1343    }
1344
1345    public void zoomOutFromSelectedItem() {
1346        mSlideshowMode = false;
1347        if (mZoomValue == getFillScreenZoomValue()) {
1348            mZoomValue = 1.0f;
1349        } else {
1350            mZoomValue /= 3.0f;
1351        }
1352        if (mZoomValue < 1.0f) {
1353            mZoomValue = 1.0f;
1354        }
1355        mHud.setAlpha(1.0f);
1356        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1357    }
1358
1359    public void rotateSelectedItems(float f) {
1360        MediaBucketList bucketList = mSelectedBucketList;
1361        ArrayList<MediaBucket> mediaBuckets = bucketList.get();
1362        DisplayList displayList = mDisplayList;
1363        int numBuckets = mediaBuckets.size();
1364        for (int i = 0; i < numBuckets; ++i) {
1365            MediaBucket bucket = mediaBuckets.get(i);
1366            ArrayList<MediaItem> mediaItems = bucket.mediaItems;
1367            if (mediaItems != null) {
1368                int numMediaItems = mediaItems.size();
1369                for (int j = 0; j < numMediaItems; ++j) {
1370                    MediaItem item = mediaItems.get(j);
1371                    DisplayItem displayItem = displayList.get(item);
1372                    displayItem.rotateImageBy(f);
1373                    displayList.addToAnimatables(displayItem);
1374                }
1375            }
1376        }
1377        if (mState == STATE_FULL_SCREEN) {
1378            centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1379        }
1380        mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
1381        // we recreate these displayitems from the cache
1382    }
1383
1384    public void cropSelectedItem() {
1385
1386    }
1387
1388    @Override
1389    public boolean onTouchEvent(MotionEvent event) {
1390        return mInputProcessor.onTouchEvent(event);
1391    }
1392
1393    @Override
1394    public boolean onKeyDown(int keyCode, KeyEvent event) {
1395        if (mInputProcessor != null)
1396            return mInputProcessor.onKeyDown(keyCode, event, mState);
1397        return false;
1398    }
1399
1400    public boolean inSlideShowMode() {
1401        return mSlideshowMode;
1402    }
1403
1404    public boolean noDeleteMode() {
1405        return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
1406    }
1407
1408    public float getZoomValue() {
1409        return mZoomValue;
1410    }
1411
1412    public boolean feedAboutToChange() {
1413        return mFeedAboutToChange;
1414    }
1415
1416    public boolean isInAlbumMode() {
1417        return mInAlbum;
1418    }
1419
1420    public Vector3f getDeltaAnchorPosition() {
1421        return mDeltaAnchorPosition;
1422    }
1423
1424    public int getExpandedSlot() {
1425        return mCurrentExpandedSlot;
1426    }
1427
1428    public GridLayoutInterface getLayoutInterface() {
1429        return (GridLayoutInterface) mLayoutInterface;
1430    }
1431
1432    public void setZoomValue(float f) {
1433        mZoomValue = f;
1434        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1435    }
1436
1437    public void setPickIntent(boolean b) {
1438        mPickIntent = b;
1439        mHud.getPathBar().popLabel();
1440        mHud.getPathBar().pushLabel(Res.drawable.icon_location_small, mContext.getResources().getString(Res.string.pick),
1441                new Runnable() {
1442                    public void run() {
1443                        if (mHud.getAlpha() == 1.0f) {
1444                            if (!mFeedAboutToChange) {
1445                                setState(STATE_MEDIA_SETS);
1446                            }
1447                        } else {
1448                            mHud.setAlpha(1.0f);
1449                        }
1450                    }
1451                });
1452    }
1453
1454    public boolean getPickIntent() {
1455        return mPickIntent;
1456    }
1457
1458    public void setViewIntent(boolean b, final String setName) {
1459        mViewIntent = b;
1460        if (b) {
1461            mMediaFeed.expandMediaSet(0);
1462            setState(STATE_GRID_VIEW);
1463            // We need to make sure we haven't pushed the same label twice
1464            if (mHud.getPathBar().getNumLevels() == 1) {
1465                mHud.getPathBar().pushLabel(Res.drawable.icon_folder_small, setName, new Runnable() {
1466                    public void run() {
1467                        if (mFeedAboutToChange) {
1468                            return;
1469                        }
1470                        if (mHud.getAlpha() == 1.0f) {
1471                            disableLocationFiltering();
1472                            if (mInputProcessor != null)
1473                                mInputProcessor.clearSelection();
1474                            setState(STATE_GRID_VIEW);
1475                        } else {
1476                            mHud.setAlpha(1.0f);
1477                        }
1478                    }
1479                });
1480            }
1481        }
1482    }
1483
1484    public boolean getViewIntent() {
1485        return mViewIntent;
1486    }
1487
1488    public void setSingleImage(boolean noDeleteMode) {
1489        mNoDeleteMode = noDeleteMode;
1490        mInputProcessor.setCurrentSelectedSlot(0);
1491    }
1492
1493    public MediaFeed getFeed() {
1494        return mMediaFeed;
1495    }
1496
1497    public void markDirty(int numFrames) {
1498        mFramesDirty = numFrames;
1499    }
1500
1501    public void focusItem(String contentUri) {
1502        mRequestFocusContentUri = contentUri;
1503        if (mMediaFeed != null) {
1504            mMediaFeed.updateListener(false);
1505        }
1506    }
1507
1508    public void onResume() {
1509        if (mMediaFeed != null) {
1510            mMediaFeed.onResume();
1511        }
1512    }
1513
1514    public void onPause() {
1515        if (mMediaFeed != null) {
1516            mMediaFeed.onPause();
1517        }
1518    }
1519
1520    public void setEnterSelectionMode(boolean enterSelection) {
1521        mRequestToEnterSelection = enterSelection;
1522    }
1523}
1524