1 package com.cooliris.media;
3 import java.util.ArrayList;
4 import javax.microedition.khronos.opengles.GL11;
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;
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;
20 public static final int ANCHOR_LEFT = 0;
21 public static final int ANCHOR_RIGHT = 1;
22 public static final int ANCHOR_CENTER = 2;
24 public static final int MAX_ITEMS_PER_SLOT = 24;
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;
29 private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
31 private HudLayer mHud;
33 private IndexRange mBufferedVisibleRange;
34 private IndexRange mVisibleRange;
35 private IndexRange mPreviousDataRange;
36 private IndexRange mCompleteRange;
37 private Pool<Vector3f> mTempVec;
38 private final ArrayList<MediaItem> mTempList = new ArrayList<MediaItem>();
39 private final MediaItem[] mTempHash = new MediaItem[64];
40 private Vector3f mDeltaAnchorPositionUncommited;
41 private Vector3f mDeltaAnchorPosition;
43 // The display primitives.
44 private GridDrawables mDrawables;
45 private float mSelectedAlpha = 0.0f;
46 private float mTargetAlpha = 0.0f;
48 private GridCamera mCamera;
49 private GridCameraManager mCameraManager;
50 private GridDrawManager mDrawManager;
51 private GridInputProcessor mInputProcessor;
53 private boolean mFeedAboutToChange;
54 private boolean mPerformingLayoutChange;
55 private boolean mFeedChanged;
57 private LayoutInterface mLayoutInterface;
58 private LayoutInterface mPrevLayoutInterface;
59 private MediaFeed mMediaFeed;
60 private boolean mInAlbum = false;
61 private int mCurrentExpandedSlot;
63 private ArrayList<MediaItem> mVisibleItems;
64 private DisplayList mDisplayList = new DisplayList();
65 private DisplayItem[] mDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
66 private DisplaySlot[] mDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
67 private float mTimeElapsedSinceTransition;
68 private BackgroundLayer mBackground;
69 private boolean mLocationFilter;
70 private float mZoomValue = 1.0f;
71 private float mCurrentFocusItemWidth = 1.0f;
72 private float mCurrentFocusItemHeight = 1.0f;
73 private float mTimeElapsedSinceGridViewReady = 0.0f;
75 private boolean mSlideshowMode;
76 private boolean mNoDeleteMode = false;
77 private float mTimeElapsedSinceView;
78 private MediaBucketList mBucketList = new MediaBucketList();
79 private float mTimeElapsedSinceStackViewReady;
80 private Pool<Vector3f> mTempVecAlt;
81 private Context mContext;
82 private RenderView mView;
83 private boolean mPickIntent;
84 private boolean mViewIntent;
85 private WakeLock mWakeLock;
86 private int mStartMemoryRange;
87 private int mFramesDirty;
88 private String mRequestFocusContentUri;
90 public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
91 mBackground = new BackgroundLayer(this);
94 Vector3f[] vectorPool = new Vector3f[128];
95 int length = vectorPool.length;
96 for (int i = 0; i < length; ++i) {
97 vectorPool[i] = new Vector3f();
99 Vector3f[] vectorPoolRenderThread = new Vector3f[128];
100 length = vectorPoolRenderThread.length;
101 for (int i = 0; i < length; ++i) {
102 vectorPoolRenderThread[i] = new Vector3f();
104 mTempVec = new Pool<Vector3f>(vectorPool);
105 mTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
106 DisplaySlot[] displaySlots = mDisplaySlots;
107 for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
108 DisplaySlot slot = new DisplaySlot();
109 displaySlots[i] = slot;
111 mLayoutInterface = layoutInterface;
112 mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
113 mDrawables = new GridDrawables(itemWidth, itemHeight);
114 mBufferedVisibleRange = new IndexRange();
115 mVisibleRange = new IndexRange();
116 mCompleteRange = new IndexRange();
117 mPreviousDataRange = new IndexRange();
118 mPreviousDataRange.begin = Shared.INVALID;
119 mPreviousDataRange.end = Shared.INVALID;
120 mDeltaAnchorPosition = new Vector3f();
121 mDeltaAnchorPositionUncommited = new Vector3f();
122 mPrevLayoutInterface = new GridLayoutInterface(1);
123 mVisibleItems = new ArrayList<MediaItem>();
124 mHud = new HudLayer(context);
125 mHud.setGridLayer(this);
126 mHud.getTimeBar().setListener(this);
127 mHud.getPathBar().pushLabel(R.drawable.icon_home_small, context.getResources().getString(R.string.app_name),
130 if (mHud.getAlpha() == 1.0f) {
131 if (!mFeedAboutToChange) {
132 setState(STATE_MEDIA_SETS);
139 mCameraManager = new GridCameraManager(mCamera);
140 mDrawManager = new GridDrawManager(context, mCamera, mDrawables, mDisplayList, mDisplayItems, mDisplaySlots);
141 mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, mTempVec, mDisplayItems);
142 setState(STATE_MEDIA_SETS);
145 public HudLayer getHud() {
149 public void shutdown() {
150 if (mMediaFeed != null) {
151 mMediaFeed.shutdown();
154 mInputProcessor = null;
157 mCameraManager = null;
163 mDisplayItems = null;
165 mDisplaySlots = null;
175 public void generate(RenderView view, RenderView.Lists lists) {
176 lists.updateList.add(this);
177 lists.opaqueList.add(this);
178 mBackground.generate(view, lists);
179 lists.blendedList.add(this);
180 lists.hitTestList.add(this);
181 mHud.generate(view, lists);
185 protected void onSizeChanged() {
186 mHud.setSize(mWidth, mHeight);
188 mBackground.setSize(mWidth, mHeight);
189 mTimeElapsedSinceTransition = 0.0f;
191 mView.requestRender();
195 public int getState() {
199 public void setState(int state) {
200 boolean feedUnchanged = false;
201 if (mState == state) {
202 feedUnchanged = true;
204 GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
205 GridLayoutInterface oldLayout = (GridLayoutInterface) mPrevLayoutInterface;
206 oldLayout.mNumRows = layoutInterface.mNumRows;
207 oldLayout.mSpacingX = layoutInterface.mSpacingX;
208 oldLayout.mSpacingY = layoutInterface.mSpacingY;
209 GridCamera camera = mCamera;
210 int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
211 MediaFeed feed = mMediaFeed;
212 boolean performLayout = true;
214 float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
215 if (yStretch < 1.0f) {
219 case STATE_GRID_VIEW:
220 mTimeElapsedSinceGridViewReady = 0.0f;
221 if (feed != null && feedUnchanged == false) {
222 boolean updatedData = feed.restorePreviousClusteringState();
224 performLayout = false;
227 layoutInterface.mNumRows = numMaxRows;
228 layoutInterface.mSpacingX = (int) (10 * Gallery.PIXEL_DENSITY);
229 layoutInterface.mSpacingY = (int) (10 * Gallery.PIXEL_DENSITY);
230 if (mState == STATE_MEDIA_SETS) {
233 MediaSet set = feed.getCurrentSet();
234 int icon = mDrawables.getIconForSet(set, true);
236 mHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
238 if (mFeedAboutToChange) {
241 if (mHud.getAlpha() == 1.0f) {
242 disableLocationFiltering();
243 mInputProcessor.clearSelection();
244 setState(STATE_GRID_VIEW);
252 if (mState == STATE_FULL_SCREEN) {
253 mHud.getPathBar().popLabel();
257 mTimeElapsedSinceStackViewReady = 0.0f;
258 if (feed != null && feedUnchanged == false) {
259 feed.performClustering();
260 performLayout = false;
262 disableLocationFiltering();
263 layoutInterface.mNumRows = numMaxRows - 1;
264 layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
265 layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
267 case STATE_FULL_SCREEN:
268 layoutInterface.mNumRows = 1;
269 layoutInterface.mSpacingX = (int) (40 * Gallery.PIXEL_DENSITY);
270 layoutInterface.mSpacingY = (int) (40 * Gallery.PIXEL_DENSITY);
271 if (mState != STATE_FULL_SCREEN) {
272 mHud.getPathBar().pushLabel(R.drawable.ic_fs_details, "", new Runnable() {
274 if (mHud.getAlpha() == 1.0f) {
275 mHud.swapFullscreenLabel();
282 case STATE_MEDIA_SETS:
283 mTimeElapsedSinceStackViewReady = 0.0f;
284 if (feed != null && feedUnchanged == false) {
285 feed.restorePreviousClusteringState();
286 feed.expandMediaSet(Shared.INVALID);
287 performLayout = false;
289 disableLocationFiltering();
290 mInputProcessor.clearSelection();
291 layoutInterface.mNumRows = numMaxRows - 1;
292 layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
293 layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
295 if (mState == STATE_FULL_SCREEN) {
296 mHud.getPathBar().popLabel();
298 mHud.getPathBar().popLabel();
304 mHud.onGridStateChanged();
305 if (performLayout && mFeedAboutToChange == false) {
306 onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
308 if (state != STATE_FULL_SCREEN) {
314 protected void enableLocationFiltering(String label) {
315 if (mLocationFilter == false) {
316 mLocationFilter = true;
317 mHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
319 if (mHud.getAlpha() == 1.0f) {
320 if (mState == STATE_FULL_SCREEN) {
321 mInputProcessor.clearSelection();
322 setState(STATE_GRID_VIEW);
324 disableLocationFiltering();
334 protected void disableLocationFiltering() {
335 if (mLocationFilter) {
336 mLocationFilter = false;
337 mMediaFeed.removeFilter();
338 mHud.getPathBar().popLabel();
343 if (mFeedAboutToChange) {
347 if (mInputProcessor.getSelectedSlot() == Shared.INVALID) {
348 if (mLocationFilter) {
349 disableLocationFiltering();
350 setState(STATE_TIMELINE);
355 case STATE_GRID_VIEW:
356 setState(STATE_MEDIA_SETS);
359 setState(STATE_GRID_VIEW);
361 case STATE_FULL_SCREEN:
362 setState(STATE_GRID_VIEW);
363 mInputProcessor.clearSelection();
371 public void endSlideshow() {
372 mSlideshowMode = false;
373 if (mWakeLock != null) {
374 if (mWakeLock.isHeld()) {
383 public void onSensorChanged(RenderView view, SensorEvent event) {
384 mInputProcessor.onSensorChanged(view, event, mState);
387 public DataSource getDataSource() {
388 if (mMediaFeed != null)
389 return mMediaFeed.getDataSource();
393 public void setDataSource(DataSource dataSource) {
394 MediaFeed feed = mMediaFeed;
397 mDisplayList.clear();
400 mMediaFeed = new MediaFeed(mContext, dataSource, this);
404 public IndexRange getVisibleRange() {
405 return mVisibleRange;
408 public IndexRange getBufferedVisibleRange() {
409 return mBufferedVisibleRange;
412 public IndexRange getCompleteRange() {
413 return mCompleteRange;
416 private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
417 int retVal = Shared.INVALID;
418 int firstSlotIndex = 0;
419 int lastSlotIndex = 0;
420 IndexRange rangeToUse = mVisibleRange;
421 synchronized (rangeToUse) {
422 firstSlotIndex = rangeToUse.begin;
423 lastSlotIndex = rangeToUse.end;
425 Pool<Vector3f> pool = mTempVec;
426 float itemWidthBy2 = itemWidth * 0.5f;
427 float itemHeightBy2 = itemHeight * 0.5f;
428 Vector3f position = pool.create();
429 Vector3f deltaAnchorPosition = pool.create();
431 deltaAnchorPosition.set(mDeltaAnchorPosition);
432 for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
433 GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
434 if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
435 position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
441 pool.delete(deltaAnchorPosition);
442 pool.delete(position);
447 void centerCameraForSlot(int slotIndex, float baseConvergence) {
448 float imageTheta = 0.0f;
449 DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
450 if (displayItem != null) {
451 imageTheta = displayItem.getImageTheta();
453 mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, mDeltaAnchorPositionUncommited,
454 mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
457 boolean constrainCameraForSlot(int slotIndex) {
458 return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, mDeltaAnchorPosition, mCurrentFocusItemWidth,
459 mCurrentFocusItemHeight);
462 // Called on render thread before rendering.
464 public boolean update(RenderView view, float timeElapsed) {
465 if (mFeedAboutToChange == false) {
466 mTimeElapsedSinceTransition += timeElapsed;
467 mTimeElapsedSinceGridViewReady += timeElapsed;
468 if (mTimeElapsedSinceGridViewReady >= 1.0f) {
469 mTimeElapsedSinceGridViewReady = 1.0f;
471 mTimeElapsedSinceStackViewReady += timeElapsed;
472 if (mTimeElapsedSinceStackViewReady >= 1.0f) {
473 mTimeElapsedSinceStackViewReady = 1.0f;
476 mTimeElapsedSinceTransition = 0;
478 if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
479 mHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
481 mHud.getPathBar().setAnimatedIcons(null);
484 // In that case, we need to commit the respective Display Items when the
486 GridCamera camera = mCamera;
487 camera.update(timeElapsed);
488 DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
489 if (anchorDisplayItem != null && !mHud.getTimeBar().isDragged()) {
490 mHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
492 mDisplayList.update(timeElapsed);
493 mInputProcessor.update(timeElapsed);
494 mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
495 if (mState == STATE_FULL_SCREEN) {
498 mHud.autoHide(false);
501 GridQuad[] fullscreenQuads = mDrawables.mFullscreenGrid;
502 int numFullScreenQuads = fullscreenQuads.length;
503 for (int i = 0; i < numFullScreenQuads; ++i) {
504 fullscreenQuads[i].update(timeElapsed);
506 if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
507 mTimeElapsedSinceView += timeElapsed;
508 if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
509 // time to go to the next slide
510 mTimeElapsedSinceView = 0.0f;
511 changeFocusToNextSlot(0.5f);
512 mCamera.commitMoveInX();
513 mCamera.commitMoveInY();
516 if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
517 mCamera.moveYTo(-0.1f);
518 mCamera.commitMoveInY();
520 boolean dirty = mDrawManager.update(timeElapsed);
521 dirty |= mSlideshowMode;
522 dirty |= mFramesDirty > 0;
523 if (mFramesDirty > 0) {
527 if (mMediaFeed != null && (mMediaFeed.getWaitingForMediaScanner())) {
528 // We limit the drawing of the frame so that the MediaScanner thread can do its work
531 } catch (InterruptedException e) {
534 if (mDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
535 || (mTimeElapsedSinceTransition > 0.0f && mTimeElapsedSinceTransition < 1.0f) || mSelectedAlpha != mTargetAlpha
536 // || (mAnimatedFov != mTargetFov)
543 private void computeVisibleRange() {
544 if (mPerformingLayoutChange)
546 if (mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited) == false) {
547 mDeltaAnchorPosition.set(mDeltaAnchorPositionUncommited);
549 mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, mDeltaAnchorPosition, mVisibleRange,
550 mBufferedVisibleRange, mCompleteRange, mState);
553 private void computeVisibleItems() {
554 if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
558 computeVisibleRange();
559 int deltaBegin = mBufferedVisibleRange.begin - mPreviousDataRange.begin;
560 int deltaEnd = mBufferedVisibleRange.end - mPreviousDataRange.end;
561 if (deltaBegin != 0 || deltaEnd != 0) {
562 // The delta has changed, we have to compute the display items again.
563 // We find the intersection range, these slots have not changed at all.
564 int firstVisibleSlotIndex = mBufferedVisibleRange.begin;
565 int lastVisibleSlotIndex = mBufferedVisibleRange.end;
566 mPreviousDataRange.begin = firstVisibleSlotIndex;
567 mPreviousDataRange.end = lastVisibleSlotIndex;
569 Pool<Vector3f> pool = mTempVec;
570 Vector3f position = pool.create();
571 Vector3f deltaAnchorPosition = pool.create();
573 MediaFeed feed = mMediaFeed;
574 DisplayList displayList = mDisplayList;
575 DisplayItem[] displayItems = mDisplayItems;
576 DisplaySlot[] displaySlots = mDisplaySlots;
577 int numDisplayItems = displayItems.length;
578 int numDisplaySlots = displaySlots.length;
579 ArrayList<MediaItem> visibleItems = mVisibleItems;
580 deltaAnchorPosition.set(mDeltaAnchorPosition);
581 LayoutInterface layout = mLayoutInterface;
582 GridCamera camera = mCamera;
583 for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
584 GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
585 MediaSet set = feed.getSetForSlot(i);
586 int indexIntoSlots = i - firstVisibleSlotIndex;
588 if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
589 ArrayList<MediaItem> items = set.getItems();
590 displaySlots[indexIntoSlots].setMediaSet(set);
591 ArrayList<MediaItem> bestItems = mTempList;
592 if (mTimeElapsedSinceTransition < 1.0f) {
593 ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, mTempHash);
596 // TODO: Could be problematic with dummy items in set.
597 int numItemsInSet = set.getNumItems();
598 int numBestItems = bestItems.size();
599 int originallyFoundItems = numBestItems;
600 if (numBestItems < MAX_ITEMS_PER_SLOT) {
601 int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
602 for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
603 MediaItem item = items.get(currItemPos);
604 if (mTimeElapsedSinceTransition >= 1.0f || !bestItems.contains(item)) {
606 if (--itemsRemaining == 0) {
612 numBestItems = bestItems.size();
613 int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
614 for (int j = 0; j < numBestItems; ++j) {
615 if (baseIndex + j >= numDisplayItems) {
618 if (j >= numItemsInSet) {
619 displayItems[baseIndex + j] = null;
621 MediaItem item = bestItems.get(j);
623 DisplayItem displayItem = displayList.get(item);
624 if (mState == STATE_FULL_SCREEN
625 || (mState == STATE_GRID_VIEW && (mTimeElapsedSinceTransition > 1.0f || j >= originallyFoundItems))) {
626 displayItem.set(position, j, false);
627 displayItem.commit();
629 displayList.setPositionAndStackIndex(displayItem, position, j, true);
631 displayItems[baseIndex + j] = displayItem;
635 for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
636 displayItems[baseIndex + j] = null;
642 mFeedChanged = false;
643 if (mInputProcessor != null && mState == STATE_FULL_SCREEN) {
644 int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
645 if (currentSelectedSlot > mCompleteRange.end)
646 currentSelectedSlot = mCompleteRange.end;
647 mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
649 if (mState == STATE_GRID_VIEW) {
650 MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
651 if (expandedSet != null) {
652 if (!mHud.getPathBar().getCurrentLabel().equals(expandedSet.mNoCountTitleString)) {
653 mHud.getPathBar().changeLabel(expandedSet.mNoCountTitleString);
657 if (mRequestFocusContentUri != null) {
658 // We have to find the item that has this contentUri
659 if (mState == STATE_FULL_SCREEN) {
660 int numSlots = mCompleteRange.end;
661 for (int i = 0; i < numSlots; ++i) {
662 MediaSet set = feed.getSetForSlot(i);
663 ArrayList<MediaItem> items = set.getItems();
664 int numItems = items.size();
665 for (int j = 0; j < numItems; ++j) {
666 if (items.get(j).mContentUri.equals(mRequestFocusContentUri)) {
667 mInputProcessor.setCurrentSelectedSlot(i);
673 mRequestFocusContentUri = null;
677 pool.delete(position);
678 pool.delete(deltaAnchorPosition);
680 // We keep upto 400 thumbnails in memory.
681 int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
682 int startMemoryRange = (mBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
683 if (mStartMemoryRange != startMemoryRange) {
684 mStartMemoryRange = startMemoryRange;
685 clearUnusedThumbnails();
691 public void handleLowMemory() {
692 clearUnusedThumbnails();
693 mDrawables.mStringTextureTable.clear();
694 mBackground.clearCache();
697 // This method can be potentially expensive
698 public void clearUnusedThumbnails() {
699 mDisplayList.clearExcept(mDisplayItems);
703 public void onSurfaceCreated(RenderView view, GL11 gl) {
704 mDisplayList.clear();
707 mDrawables.mStringTextureTable.clear();
708 mDrawables.onSurfaceCreated(view, gl);
712 public void onSurfaceChanged(RenderView view, int width, int height) {
713 mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
714 view.setFov(mCamera.mFov);
718 // Renders the node in a given pass.
719 public void renderOpaque(RenderView view, GL11 gl) {
720 GridCamera camera = mCamera;
721 int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
722 computeVisibleItems();
724 gl.glMatrixMode(GL11.GL_MODELVIEW);
726 GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
727 camera.mUpX, camera.mUpY, camera.mUpZ);
729 if (mSelectedAlpha != 1.0f) {
730 gl.glEnable(GL11.GL_BLEND);
731 gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
732 view.setAlpha(mSelectedAlpha);
734 if (selectedSlotIndex != Shared.INVALID) {
739 mDrawManager.prepareDraw(mBufferedVisibleRange, mVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
740 mInputProcessor.isFocusItemPressed());
741 if (mSelectedAlpha != 0.0f) {
742 mDrawManager.drawThumbnails(view, gl, mState);
744 gl.glDisable(GL11.GL_BLEND);
745 // We draw the selected slotIndex.
746 if (selectedSlotIndex != Shared.INVALID) {
747 mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
748 mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
749 mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
751 view.setAlpha(mSelectedAlpha);
754 public void renderBlended(RenderView view, GL11 gl) {
755 // We draw the placeholder for all visible slots.
756 if (mHud != null && mDrawManager != null) {
757 mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(), mTimeElapsedSinceStackViewReady,
758 mTimeElapsedSinceGridViewReady, mBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
759 || mMediaFeed.isLoading());
763 public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
764 if (mPerformingLayoutChange || !mDeltaAnchorPosition.equals(mDeltaAnchorPositionUncommited)) {
768 mTimeElapsedSinceTransition = 0.0f;
769 mPerformingLayoutChange = true;
770 LayoutInterface layout = mLayoutInterface;
771 if (oldLayout == null) {
772 oldLayout = mPrevLayoutInterface;
774 GridCamera camera = mCamera;
775 if (currentAnchorSlotIndex == Shared.INVALID) {
776 currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
777 if (mCurrentExpandedSlot != Shared.INVALID) {
778 currentAnchorSlotIndex = mCurrentExpandedSlot;
780 int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
781 if (selectedSlotIndex != Shared.INVALID) {
782 currentAnchorSlotIndex = selectedSlotIndex;
785 if (newAnchorSlotIndex == Shared.INVALID) {
786 newAnchorSlotIndex = currentAnchorSlotIndex;
788 int itemHeight = camera.mItemHeight;
789 int itemWidth = camera.mItemWidth;
790 Pool<Vector3f> pool = mTempVec;
791 Vector3f deltaAnchorPosition = pool.create();
792 Vector3f currentSlotPosition = pool.create();
794 deltaAnchorPosition.set(0, 0, 0);
795 if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
796 layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
797 oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
798 currentSlotPosition.subtract(mDeltaAnchorPosition);
799 deltaAnchorPosition.subtract(currentSlotPosition);
800 deltaAnchorPosition.y = 0;
801 deltaAnchorPosition.z = 0;
803 mDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
805 pool.delete(deltaAnchorPosition);
806 pool.delete(currentSlotPosition);
808 centerCameraForSlot(newAnchorSlotIndex, 1.0f);
809 mCurrentExpandedSlot = Shared.INVALID;
811 // Force recompute of visible items and their positions.
812 ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
813 ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
814 ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
815 forceRecomputeVisibleRange();
816 mPerformingLayoutChange = false;
819 private void forceRecomputeVisibleRange() {
820 mPreviousDataRange.begin = Shared.INVALID;
821 mPreviousDataRange.end = Shared.INVALID;
823 mView.requestRender();
827 // called on background thread
828 public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
831 forceRecomputeVisibleRange();
832 if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
833 mHud.setFeed(feed, mState, needsLayout);
837 while (mPerformingLayoutChange == true) {
840 if (mState == STATE_GRID_VIEW) {
842 MediaSet set = feed.getCurrentSet();
843 if (set != null && !mLocationFilter)
844 mHud.getPathBar().changeLabel(set.mNoCountTitleString);
847 DisplayItem[] displayItems = mDisplayItems;
848 int firstBufferedVisibleSlotIndex = mBufferedVisibleRange.begin;
849 int lastBufferedVisibleSlotIndex = mBufferedVisibleRange.end;
850 int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
851 if (mCurrentExpandedSlot != Shared.INVALID) {
852 currentlyVisibleSlotIndex = mCurrentExpandedSlot;
854 MediaItem anchorItem = null;
855 ArrayList<MediaItem> visibleItems = mVisibleItems;
856 visibleItems.clear();
857 visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
858 if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
859 && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
860 int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
861 for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
862 DisplayItem displayItem = displayItems[baseIndex + i];
863 if (displayItem != null) {
864 if (anchorItem == null) {
865 anchorItem = displayItem.mItemRef;
867 visibleItems.add(displayItem.mItemRef);
871 // We want to add items from the middle.
872 int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
873 int midPoint = (lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex) / 2;
874 int length = displayItems.length;
875 for (int i = 0; i < numItems; ++i) {
876 int index = midPoint + Shared.midPointIterator(i);
877 int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
878 if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
879 for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
880 DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
881 if (displayItem != null) {
882 MediaItem item = displayItem.mItemRef;
883 if (item != anchorItem) {
884 visibleItems.add(item);
890 int newSlotIndex = Shared.INVALID;
891 if (anchorItem != null) {
892 // We try to find the anchor item in the new feed.
893 int numSlots = feed.getNumSlots();
894 for (int i = 0; i < numSlots; ++i) {
895 MediaSet set = feed.getSetForSlot(i);
896 if (set != null && set.containsItem(anchorItem)) {
903 if (anchorItem != null && newSlotIndex == Shared.INVALID) {
904 int numSlots = feed.getNumSlots();
905 MediaSet parentSet = anchorItem.mParentMediaSet;
906 for (int i = 0; i < numSlots; ++i) {
907 MediaSet set = feed.getSetForSlot(i);
908 if (set != null && set.mId == parentSet.mId) {
915 // We must create a new display store now since the data has changed.
916 if (newSlotIndex != Shared.INVALID) {
917 if (mState == STATE_MEDIA_SETS) {
918 mDisplayList.clearExcept(displayItems);
920 onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
922 forceRecomputeVisibleRange();
924 mCurrentExpandedSlot = Shared.INVALID;
925 mFeedAboutToChange = false;
928 mHud.setFeed(feed, mState, needsLayout);
931 mView.requestRender();
935 public DisplayItem getAnchorDisplayItem(int type) {
936 int slotIndex = getAnchorSlotIndex(type);
937 return mDisplayItems[(slotIndex - mBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT];
940 public float getScrollPosition() {
941 return (mCamera.mLookAtX * mCamera.mScale + mDeltaAnchorPosition.x); // in
945 public DisplayItem getDisplayItemForScrollPosition(float posX) {
946 Pool<Vector3f> pool = mTempVecAlt;
947 MediaFeed feed = mMediaFeed;
948 int itemWidth = mCamera.mItemWidth;
949 int itemHeight = mCamera.mItemHeight;
950 GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
951 float absolutePosX = posX;
952 int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
953 int right = feed == null ? 0 : (int) (feed.getNumSlots());
955 Vector3f position = pool.create();
957 for (int i = left; i < right; ++i) {
958 gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
960 if (position.x >= absolutePosX) {
965 pool.delete(position);
967 if (mFeedAboutToChange) {
970 right = feed == null ? 0 : feed.getNumSlots();
975 if (retSlot >= right)
977 MediaSet set = feed.getSetForSlot(retSlot);
979 ArrayList<MediaItem> items = set.getItems();
980 if (items != null && set.getNumItems() > 0) {
981 return (mDisplayList.get(items.get(0)));
987 // Returns the top left-most item.
988 public int getAnchorSlotIndex(int anchorType) {
990 switch (anchorType) {
992 retVal = mVisibleRange.begin;
995 retVal = mVisibleRange.end;
998 retVal = (mVisibleRange.begin + mVisibleRange.end) / 2;
1004 DisplayItem getDisplayItemForSlotId(int slotId) {
1005 int index = slotId - mBufferedVisibleRange.begin;
1006 if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
1007 return mDisplayItems[index * MAX_ITEMS_PER_SLOT];
1012 boolean changeFocusToNextSlot(float convergence) {
1013 int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
1014 boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
1015 if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
1017 mHud.setAlpha(1.0f);
1022 boolean changeFocusToSlot(int slotId, float convergence) {
1024 int index = slotId - mBufferedVisibleRange.begin;
1025 if (index >= 0 && slotId <= mBufferedVisibleRange.end) {
1026 DisplayItem displayItem = mDisplayItems[index * MAX_ITEMS_PER_SLOT];
1027 if (displayItem != null) {
1028 MediaItem item = displayItem.mItemRef;
1029 mHud.fullscreenSelectionChanged(item, slotId + 1, mCompleteRange.end + 1);
1030 if (slotId != Shared.INVALID && slotId <= mCompleteRange.end) {
1031 mInputProcessor.setCurrentFocusSlot(slotId);
1032 centerCameraForSlot(slotId, convergence);
1035 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
1043 boolean changeFocusToPreviousSlot(float convergence) {
1044 return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
1047 public ArrayList<MediaBucket> getSelectedBuckets() {
1048 return mBucketList.get();
1051 public void selectAll() {
1052 if (mState != STATE_FULL_SCREEN) {
1053 int numSlots = mCompleteRange.end + 1;
1054 for (int i = 0; i < numSlots; ++i) {
1055 addSlotToSelectedItems(i, false, false);
1057 updateCountOfSelectedItems();
1059 addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
1063 public void deselectOrCancelSelectMode() {
1064 if (mBucketList.size() == 0) {
1065 mHud.cancelSelection();
1067 mBucketList.clear();
1068 updateCountOfSelectedItems();
1072 public void deselectAll() {
1073 mHud.cancelSelection();
1074 mBucketList.clear();
1075 updateCountOfSelectedItems();
1078 public void deleteSelection() {
1079 // Delete the selection and exit selection mode.
1080 mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
1083 // If the current set is now empty, return to the parent set.
1084 if (mCompleteRange.isEmpty()) {
1085 goBack(); // TODO(venkat): This does not work most of the time, can you take a look?
1089 void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
1090 if (mFeedAboutToChange == false) {
1091 MediaFeed feed = mMediaFeed;
1092 mBucketList.add(slotId, feed, removeIfAlreadyAdded);
1094 updateCountOfSelectedItems();
1095 if (mBucketList.size() == 0)
1101 private void updateCountOfSelectedItems() {
1102 mHud.updateNumItemsSelected(mBucketList.size());
1105 public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
1106 return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * Gallery.PIXEL_DENSITY), mCamera.mItemHeight
1107 + (int) (100 * Gallery.PIXEL_DENSITY));
1110 public int getSlotIndexForScreenPosition(int posX, int posY) {
1111 return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
1114 private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
1115 Pool<Vector3f> pool = mTempVec;
1117 Vector3f worldPos = pool.create();
1119 GridCamera camera = mCamera;
1120 camera.convertToCameraSpace(posX, posY, 0, worldPos);
1121 // slots are expressed in pixels as well
1122 worldPos.x *= camera.mScale;
1123 worldPos.y *= camera.mScale;
1125 retVal = hitTest(worldPos, itemWidth, itemHeight);
1127 pool.delete(worldPos);
1132 public boolean tapGesture(int slotIndex, boolean metadata) {
1133 MediaFeed feed = mMediaFeed;
1134 if (!feed.isClustered()) {
1135 // It is not clustering.
1136 if (!feed.hasExpandedMediaSet()) {
1137 if (feed.canExpandSet(slotIndex)) {
1138 mCurrentExpandedSlot = slotIndex;
1139 feed.expandMediaSet(slotIndex);
1140 setState(STATE_GRID_VIEW);
1147 // Select a cluster, and recompute a new cluster within this cluster.
1148 mCurrentExpandedSlot = slotIndex;
1151 DisplaySlot slot = mDisplaySlots[slotIndex - mBufferedVisibleRange.begin];
1152 if (slot.hasValidLocation()) {
1153 MediaSet set = slot.getMediaSet();
1154 if (set.mReverseGeocodedLocation != null) {
1155 enableLocationFiltering(set.mReverseGeocodedLocation);
1157 feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude, set.mMaxLonLongitude));
1164 public void onTimeChanged(TimeBar timebar) {
1165 if (mFeedAboutToChange) {
1168 // TODO lot of optimization possible here
1169 MediaItem item = timebar.getItem();
1170 MediaFeed feed = mMediaFeed;
1171 int numSlots = feed.getNumSlots();
1172 for (int i = 0; i < numSlots; ++i) {
1173 MediaSet set = feed.getSetForSlot(i);
1177 ArrayList<MediaItem> items = set.getItems();
1178 if (items == null || set.getNumItems() == 0) {
1181 if (items.contains(item)) {
1182 centerCameraForSlot(i, 1.0f);
1188 public void onFeedAboutToChange(MediaFeed feed) {
1189 mFeedAboutToChange = true;
1190 mTimeElapsedSinceTransition = 0;
1193 public void startSlideshow() {
1195 mSlideshowMode = true;
1197 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1198 mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
1200 PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1201 mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
1202 mWakeLock.acquire();
1205 public void enterSelectionMode() {
1206 mSlideshowMode = false;
1207 mHud.enterSelectionMode();
1208 int currentSlot = mInputProcessor.getCurrentSelectedSlot();
1209 if (currentSlot == Shared.INVALID) {
1210 currentSlot = mInputProcessor.getCurrentFocusSlot();
1212 addSlotToSelectedItems(currentSlot, false, true);
1215 private float getFillScreenZoomValue() {
1216 return GridCameraManager.getFillScreenZoomValue(mCamera, mTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
1219 public void zoomInToSelectedItem() {
1220 mSlideshowMode = false;
1221 float potentialZoomValue = getFillScreenZoomValue();
1222 if (mZoomValue < potentialZoomValue) {
1223 mZoomValue = potentialZoomValue;
1227 if (mZoomValue > 6.0f) {
1230 mHud.setAlpha(1.0f);
1231 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1234 public void zoomOutFromSelectedItem() {
1235 mSlideshowMode = false;
1236 if (mZoomValue == getFillScreenZoomValue()) {
1241 if (mZoomValue < 1.0f) {
1244 mHud.setAlpha(1.0f);
1245 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1248 public void rotateSelectedItems(float f) {
1249 MediaBucketList bucketList = mBucketList;
1250 ArrayList<MediaBucket> mediaBuckets = bucketList.get();
1251 DisplayList displayList = mDisplayList;
1252 int numBuckets = mediaBuckets.size();
1253 for (int i = 0; i < numBuckets; ++i) {
1254 MediaBucket bucket = mediaBuckets.get(i);
1255 ArrayList<MediaItem> mediaItems = bucket.mediaItems;
1256 if (mediaItems != null) {
1257 int numMediaItems = mediaItems.size();
1258 for (int j = 0; j < numMediaItems; ++j) {
1259 MediaItem item = mediaItems.get(j);
1260 DisplayItem displayItem = displayList.get(item);
1261 displayItem.rotateImageBy(f);
1262 displayList.addToAnimatables(displayItem);
1266 if (mState == STATE_FULL_SCREEN) {
1267 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1269 mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
1270 // we recreate these displayitems from the cache
1273 public void cropSelectedItem() {
1278 public boolean onTouchEvent(MotionEvent event) {
1279 return mInputProcessor.onTouchEvent(event);
1283 public boolean onKeyDown(int keyCode, KeyEvent event) {
1284 if (mInputProcessor != null)
1285 return mInputProcessor.onKeyDown(keyCode, event, mState);
1289 public boolean inSlideShowMode() {
1290 return mSlideshowMode;
1293 public boolean noDeleteMode() {
1294 return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
1297 public float getZoomValue() {
1301 public boolean feedAboutToChange() {
1302 return mFeedAboutToChange;
1305 public boolean isInAlbumMode() {
1309 public Vector3f getDeltaAnchorPosition() {
1310 return mDeltaAnchorPosition;
1313 public int getExpandedSlot() {
1314 return mCurrentExpandedSlot;
1317 public GridLayoutInterface getLayoutInterface() {
1318 return (GridLayoutInterface) mLayoutInterface;
1321 public void setZoomValue(float f) {
1323 centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1326 public void setPickIntent(boolean b) {
1328 mHud.getPathBar().popLabel();
1329 mHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
1332 if (mHud.getAlpha() == 1.0f) {
1333 if (!mFeedAboutToChange) {
1334 setState(STATE_MEDIA_SETS);
1337 mHud.setAlpha(1.0f);
1343 public boolean getPickIntent() {
1347 public void setViewIntent(boolean b, final String setName) {
1350 mMediaFeed.expandMediaSet(0);
1351 setState(STATE_GRID_VIEW);
1352 // We need to make sure we haven't pushed the same label twice
1353 if (mHud.getPathBar().getNumLevels() == 1) {
1354 mHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
1356 if (mFeedAboutToChange) {
1359 if (mHud.getAlpha() == 1.0f) {
1360 disableLocationFiltering();
1361 mInputProcessor.clearSelection();
1362 setState(STATE_GRID_VIEW);
1364 mHud.setAlpha(1.0f);
1372 public boolean getViewIntent() {
1376 public void setSingleImage(boolean noDeleteMode) {
1377 mNoDeleteMode = noDeleteMode;
1378 mInputProcessor.setCurrentSelectedSlot(0);
1381 public MediaFeed getFeed() {
1385 public void markDirty(int numFrames) {
1386 mFramesDirty = numFrames;
1389 public void focusItem(String contentUri) {
1390 mRequestFocusContentUri = contentUri;
1391 mMediaFeed.updateListener(false);