OSDN Git Service

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