Prevent locking of files when the sd card is ejected New Cache Dirty system which will work on any arbitrary directory Non-static hudlayer and pathbar TouchEventQueue has a max size before it starts dropping touch_move events Other bug fixes
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a4c29ea..33c8e51 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -131,6 +131,10 @@
                 <action android:name="android.intent.action.MEDIA_MOUNTED" />
                 <data android:scheme="file" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_EJECT" />
+                <data android:scheme="file" />
+            </intent-filter>
 		</receiver>
 		<receiver android:name="PhotoAppWidgetProvider" android:label="@string/gadget_title">
             <intent-filter>
diff --git a/src/com/cooliris/cache/BootReceiver.java b/src/com/cooliris/cache/BootReceiver.java
index dae5355..e19aba6 100644
--- a/src/com/cooliris/cache/BootReceiver.java
+++ b/src/com/cooliris/cache/BootReceiver.java
@@ -1,6 +1,7 @@
 package com.cooliris.cache;
 
 import com.cooliris.media.LocalDataSource;
+import com.cooliris.media.PicasaDataSource;
 import com.cooliris.media.SingleDataSource;
 
 import android.content.BroadcastReceiver;
@@ -50,6 +51,12 @@
             ContentResolver cr = context.getContentResolver();
             cr.registerContentObserver(uriImages, false, localObserver);
             cr.registerContentObserver(uriVideos, false, localObserver);
+        } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+        	LocalDataSource.sThumbnailCache.close();
+        	LocalDataSource.sThumbnailCacheVideo.close();
+        	PicasaDataSource.sThumbnailCache.close();
+        	CacheService.sAlbumCache.close();
+        	CacheService.sMetaAlbumCache.close();
         }
     }
 }
diff --git a/src/com/cooliris/cache/CacheService.java b/src/com/cooliris/cache/CacheService.java
index d54f9fa..bf20af2 100644
--- a/src/com/cooliris/cache/CacheService.java
+++ b/src/com/cooliris/cache/CacheService.java
@@ -19,6 +19,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 import android.app.IntentService;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -56,7 +57,7 @@
 public final class CacheService extends IntentService {
 	public static final String ACTION_CACHE = "com.cooliris.cache.action.CACHE";
 	public static final DiskCache sAlbumCache = new DiskCache("local-album-cache");
-	public static final DiskCache sMetaAlbumCache = new DiskCache("local-meta-album-cache");
+	public static final DiskCache sMetaAlbumCache = new DiskCache("local-meta-cache");
 
 	private static final String TAG = "CacheService";
 	private static ImageList sList = null;
@@ -94,7 +95,7 @@
 	        Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION };
 
 	public static final String[] SENSE_PROJECTION = new String[] { Images.ImageColumns.BUCKET_ID,
-	        "MAX(" + Images.ImageColumns.DATE_ADDED + ")" };
+	        "MAX(" + Images.ImageColumns.DATE_ADDED + "), COUNT(*)" };
 
 	// Must preserve order between these indices and the order of the terms in
 	// INITIAL_PROJECTION_IMAGES and
@@ -204,10 +205,10 @@
 					if (ids != null && observer != null) {
 						observer.onChange(ids);
 					}
-					if (ids.length > 0) {
+					if (ids != null && ids.length > 0) {
 						sList = null;
+						Log.i(TAG, "Done computing dirty sets for num " + ids.length);
 					}
-					Log.i(TAG, "Done computing dirty sets for num " + ids.length);
 				}
 			});
 		} else {
@@ -556,12 +557,10 @@
 		}
 		byte[] bitmap = thumbnailCache.get(thumbId, timestamp);
 		if (bitmap == null) {
-			Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
 			final long time = SystemClock.uptimeMillis();
 			bitmap = buildThumbnailForId(context, thumbnailCache, thumbId, origId, isVideo, DEFAULT_THUMBNAIL_WIDTH,
 			        DEFAULT_THUMBNAIL_HEIGHT);
 			Log.i(TAG, "Built thumbnail and screennail for " + origId + " in " + (SystemClock.uptimeMillis() - time));
-			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 		}
 		return bitmap;
 	}
@@ -608,7 +607,6 @@
 					return null;
 				}
 			} else {
-				Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
 				new Thread() {
 					public void run() {
 						try {
@@ -883,13 +881,22 @@
 		lBuffer.put(0, l);
 		return bArray;
 	}
+	
+	private static final byte[] longArrayToByteArray(final long[] l) {
+		final byte[] bArray = new byte[8 * l.length];
+		final ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
+		final LongBuffer lBuffer = bBuffer.asLongBuffer();
+		int numLongs = l.length;
+		for (int i = 0; i < numLongs; ++i) {
+			lBuffer.put(i, l[i]);
+		}
+		return bArray;
+	}
 
 	private final static void refresh(final Context context) {
 		// First we build the album cache.
 		// This is the meta-data about the albums / buckets on the SD card.
 		Log.i(TAG, "Refreshing cache.");
-		int priority = Process.getThreadPriority(Process.myTid());
-		Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);
 		sAlbumCache.deleteAll();
 		putLocaleForAlbumCache(Locale.getDefault());
 
@@ -944,7 +951,6 @@
 		// Now we must cache the items contained in every album / bucket.
 		populateMediaItemsForSets(context, sets, acceleratedSets, false);
 		sAlbumCache.delete(ALBUM_CACHE_INCOMPLETE_INDEX);
-		Process.setThreadPriority(priority);
 
 		// Complete any queued dirty requests
 		processQueuedDirty(context);
@@ -973,36 +979,52 @@
 	}
 
 	private static final long[] computeDirtySets(final Context context) {
-		final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
-		final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
+		final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+		final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
 		final ContentResolver cr = context.getContentResolver();
-
-		final Cursor cursorImages = cr.query(uriImages, SENSE_PROJECTION, null, null, null);
-		final Cursor cursorVideos = cr.query(uriVideos, SENSE_PROJECTION, null, null, null);
+		final String where = Images.ImageColumns.BUCKET_ID + "!=0) GROUP BY (" + Images.ImageColumns.BUCKET_ID + " ";
+		final Cursor cursorImages = cr.query(uriImages, SENSE_PROJECTION, where, null, null);
+		final Cursor cursorVideos = cr.query(uriVideos, SENSE_PROJECTION, where, null, null);
 		Cursor[] cursors = new Cursor[2];
 		cursors[0] = cursorImages;
 		cursors[1] = cursorVideos;
 		final MergeCursor cursor = new MergeCursor(cursors);
 		long[] retVal = null;
+		int ctr = 0;
 		try {
 			if (cursor.moveToFirst()) {
 				retVal = new long[cursor.getCount()];
-				int ctr = 0;
 				boolean allDirty = false;
 				do {
 					long setId = cursor.getLong(0);
-					retVal[ctr++] = setId;
-					byte[] data = sMetaAlbumCache.get(setId, 0);
-					if (data == null) {
-						// We need to refresh everything.
-						markDirty(context);
-						allDirty = true;
-					}
-					if (!allDirty) {
-						long maxAdded = cursor.getLong(1);
-						long oldMaxAdded = toLong(data);
-						if (maxAdded > oldMaxAdded) {
-							markDirty(context, setId);
+					if (allDirty) {
+						retVal[ctr++] = setId;
+					} else {
+						boolean contains = sAlbumCache.isDataAvailable(setId, 0);
+						if (!contains) {
+							// We need to refresh everything.
+							markDirty(context);
+							retVal[ctr++] = setId;
+							allDirty = true;
+						}
+						if (!allDirty) {
+							long maxAdded = cursor.getLong(1);
+							int count = cursor.getInt(2);
+							byte[] data = sMetaAlbumCache.get(setId, 0);
+							long[] dataLong = new long[2];
+							if (data != null) {
+								dataLong = toLongArray(data);
+							}
+							long oldMaxAdded = dataLong[0];
+							long oldCount = dataLong[1];
+							Log.i(TAG, "Bucket " + setId + " Old added " + oldMaxAdded + " count " + oldCount + " New added " + maxAdded + " count " + count);
+							if (maxAdded > oldMaxAdded || oldCount != count) {
+								markDirty(context, setId);
+								retVal[ctr++] = setId;
+								dataLong[0] = maxAdded;
+								dataLong[1] = count;
+								sMetaAlbumCache.put(setId, longArrayToByteArray(dataLong));
+							}
 						}
 					}
 				} while (cursor.moveToNext());
@@ -1010,8 +1032,13 @@
 		} finally {
 			cursor.close();
 		}
+		sMetaAlbumCache.flush();
 		processQueuedDirty(context);
-		return retVal;
+		long[] retValCompact = new long[ctr];
+		for (int i = 0; i < ctr; ++i) {
+			retValCompact[i] = retVal[i];
+		}
+		return retValCompact;
 	}
 
 	private static final void processQueuedDirty(final Context context) {
@@ -1074,7 +1101,6 @@
 				final int approximateCountPerSet = count / numSets;
 				for (int i = 0; i < numSets; ++i) {
 					final MediaSet set = sets.get(i);
-					set.getItems().clear();
 					set.setNumExpectedItems(approximateCountPerSet);
 				}
 				do {
@@ -1091,7 +1117,7 @@
 					final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX);
 					final MediaSet set = findSet(setId, acceleratedSets);
 					if (set != null) {
-						set.getItems().add(item);
+						set.addItem(item);
 					}
 				} while (sortCursor.moveToNext());
 			}
@@ -1144,20 +1170,9 @@
 			}
 			writeItemsForASet(sets.get(i));
 		}
-		writeMetaAlbumCache(sets);
 		sAlbumCache.flush();
 	}
 
-	private static final void writeMetaAlbumCache(ArrayList<MediaSet> sets) {
-		final int numSets = sets.size();
-		for (int i = 0; i < numSets; ++i) {
-			final MediaSet set = sets.get(i);
-			byte[] data = longToByteArray(set.mMaxAddedTimestamp);
-			sMetaAlbumCache.put(set.mId, data);
-		}
-		sMetaAlbumCache.flush();
-	}
-
 	private static final void writeItemsForASet(final MediaSet set) {
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
diff --git a/src/com/cooliris/media/Gallery.java b/src/com/cooliris/media/Gallery.java
index 46fc3dd..dcadb4d 100644
--- a/src/com/cooliris/media/Gallery.java
+++ b/src/com/cooliris/media/Gallery.java
@@ -124,7 +124,7 @@
 			final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
 			mGridLayer.setDataSource(singleCombinedDataSource);
 			mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
-			if (SingleDataSource.isSingleImageMode(uri.toString())) {
+			if (singleDataSource.isSingleImage()) {
 				mGridLayer.setSingleImage(false);
 			} else if (slideshow) {
 				mGridLayer.setSingleImage(true);
diff --git a/src/com/cooliris/media/GridLayer.java b/src/com/cooliris/media/GridLayer.java
index 8cd05c2..61bea47 100644
--- a/src/com/cooliris/media/GridLayer.java
+++ b/src/com/cooliris/media/GridLayer.java
@@ -7,7 +7,6 @@
 import android.opengl.GLU;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.content.Context;
@@ -29,7 +28,7 @@
 
 	private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
 
-	private static HudLayer sHud;
+	private HudLayer mHud;
 	private int mState;
 	private static final IndexRange sBufferedVisibleRange = new IndexRange();
 	private static final IndexRange sVisibleRange = new IndexRange();
@@ -133,33 +132,32 @@
 		sBucketList.clear();
 
 		sVisibleItems = new ArrayList<MediaItem>();
-		if (sHud == null) {
-			sHud = new HudLayer(context);
-		}
-		sHud.setContext(context);
-		sHud.setGridLayer(this);
-		sHud.getPathBar().clear();
-		sHud.setGridLayer(this);
-		sHud.getTimeBar().setListener(this);
-		sHud.getPathBar().pushLabel(R.drawable.icon_home_small, context.getResources().getString(R.string.app_name),
+		mHud = new HudLayer(context);
+		mHud.setContext(context);
+		mHud.setGridLayer(this);
+		mHud.getPathBar().clear();
+		mHud.setGridLayer(this);
+		mHud.getTimeBar().setListener(this);
+		mHud.getPathBar().pushLabel(R.drawable.icon_home_small, context.getResources().getString(R.string.app_name),
 		        new Runnable() {
 			        public void run() {
-				        if (sHud.getAlpha() == 1.0f) {
+				        if (mHud.getAlpha() == 1.0f) {
 					        if (!mFeedAboutToChange) {
 						        setState(STATE_MEDIA_SETS);
 					        }
 				        } else {
-					        sHud.setAlpha(1.0f);
+					        mHud.setAlpha(1.0f);
 				        }
 			        }
 		        });
 		mCameraManager = new GridCameraManager(mCamera);
 		mDrawManager = new GridDrawManager(context, mCamera, mDrawables, sDisplayList, sDisplayItems, sDisplaySlots);
 		mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, sTempVec, sDisplayItems);
+		setState(STATE_MEDIA_SETS);
 	}
 
 	public HudLayer getHud() {
-		return sHud;
+		return mHud;
 	}
 
 	public void shutdown() {
@@ -185,13 +183,13 @@
 		mBackground.generate(view, lists);
 		lists.blendedList.add(this);
 		lists.hitTestList.add(this);
-		sHud.generate(view, lists);
+		mHud.generate(view, lists);
 	}
 
 	@Override
 	protected void onSizeChanged() {
-		sHud.setSize(mWidth, mHeight);
-		sHud.setAlpha(1.0f);
+		mHud.setSize(mWidth, mHeight);
+		mHud.setAlpha(1.0f);
 		mBackground.setSize(mWidth, mHeight);
 		mTimeElapsedSinceTransition = 0.0f;
 		if (mView != null) {
@@ -240,24 +238,24 @@
 				MediaSet set = feed.getCurrentSet();
 				int icon = mDrawables.getIconForSet(set, true);
 				if (set != null) {
-					sHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
+					mHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
 						public void run() {
 							if (mFeedAboutToChange) {
 								return;
 							}
-							if (sHud.getAlpha() == 1.0f) {
+							if (mHud.getAlpha() == 1.0f) {
 								disableLocationFiltering();
 								mInputProcessor.clearSelection();
 								setState(STATE_GRID_VIEW);
 							} else {
-								sHud.setAlpha(1.0f);
+								mHud.setAlpha(1.0f);
 							}
 						}
 					});
 				}
 			}
 			if (mState == STATE_FULL_SCREEN) {
-				sHud.getPathBar().popLabel();
+				mHud.getPathBar().popLabel();
 			}
 			break;
 		case STATE_TIMELINE:
@@ -276,12 +274,12 @@
 			layoutInterface.mSpacingX = (int) (40 * Gallery.PIXEL_DENSITY);
 			layoutInterface.mSpacingY = (int) (40 * Gallery.PIXEL_DENSITY);
 			if (mState != STATE_FULL_SCREEN) {
-				sHud.getPathBar().pushLabel(R.drawable.ic_fs_details, "", new Runnable() {
+				mHud.getPathBar().pushLabel(R.drawable.ic_fs_details, "", new Runnable() {
 					public void run() {
-						if (sHud.getAlpha() == 1.0f) {
-							sHud.swapFullscreenLabel();
+						if (mHud.getAlpha() == 1.0f) {
+							mHud.swapFullscreenLabel();
 						}
-						sHud.setAlpha(1.0f);
+						mHud.setAlpha(1.0f);
 					}
 				});
 			}
@@ -300,15 +298,15 @@
 			layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
 			if (mInAlbum) {
 				if (mState == STATE_FULL_SCREEN) {
-					sHud.getPathBar().popLabel();
+					mHud.getPathBar().popLabel();
 				}
-				sHud.getPathBar().popLabel();
+				mHud.getPathBar().popLabel();
 				mInAlbum = false;
 			}
 			break;
 		}
 		mState = state;
-		sHud.onGridStateChanged();
+		mHud.onGridStateChanged();
 		if (performLayout && mFeedAboutToChange == false) {
 			onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
 		}
@@ -321,9 +319,9 @@
 	protected void enableLocationFiltering(String label) {
 		if (mLocationFilter == false) {
 			mLocationFilter = true;
-			sHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
+			mHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
 				public void run() {
-					if (sHud.getAlpha() == 1.0f) {
+					if (mHud.getAlpha() == 1.0f) {
 						if (mState == STATE_FULL_SCREEN) {
 							mInputProcessor.clearSelection();
 							setState(STATE_GRID_VIEW);
@@ -331,7 +329,7 @@
 							disableLocationFiltering();
 						}
 					} else {
-						sHud.setAlpha(1.0f);
+						mHud.setAlpha(1.0f);
 					}
 				}
 			});
@@ -342,7 +340,7 @@
 		if (mLocationFilter) {
 			mLocationFilter = false;
 			mMediaFeed.removeFilter();
-			sHud.getPathBar().popLabel();
+			mHud.getPathBar().popLabel();
 		}
 	}
 
@@ -383,7 +381,7 @@
 			}
 			mWakeLock = null;
 		}
-		sHud.setAlpha(1.0f);
+		mHud.setAlpha(1.0f);
 	}
 
 	@Override
@@ -490,9 +488,9 @@
 				hud.setMode(HudLayer.MODE_NORMAL);
 		}
 		if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
-			sHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
+			mHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
 		} else {
-			sHud.getPathBar().setAnimatedIcons(null);
+			mHud.getPathBar().setAnimatedIcons(null);
 		}
 
 		// In that case, we need to commit the respective Display Items when the
@@ -500,17 +498,17 @@
 		GridCamera camera = mCamera;
 		camera.update(timeElapsed);
 		DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
-		if (anchorDisplayItem != null && !sHud.getTimeBar().isDragged()) {
-			sHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
+		if (anchorDisplayItem != null && !mHud.getTimeBar().isDragged()) {
+			mHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
 		}
 		sDisplayList.update(timeElapsed);
 		mInputProcessor.update(timeElapsed);
 		mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
 		if (mState == STATE_FULL_SCREEN) {
-			sHud.autoHide(true);
+			mHud.autoHide(true);
 		} else {
-			sHud.autoHide(false);
-			sHud.setAlpha(1.0f);
+			mHud.autoHide(false);
+			mHud.setAlpha(1.0f);
 		}
 		GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
 		int numFullScreenQuads = fullscreenQuads.length;
@@ -670,8 +668,8 @@
 					if (mState == STATE_GRID_VIEW) {
 						MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
 						if (expandedSet != null) {
-							if (!sHud.getPathBar().getCurrentLabel().equals(expandedSet.mNoCountTitleString)) {
-								sHud.getPathBar().changeLabel(expandedSet.mNoCountTitleString);
+							if (!mHud.getPathBar().getCurrentLabel().equals(expandedSet.mNoCountTitleString)) {
+								mHud.getPathBar().changeLabel(expandedSet.mNoCountTitleString);
 							}
 						}
 					}
@@ -688,13 +686,13 @@
 									if (itemUri != null && mRequestFocusContentUri != null) {
 										if (itemUri.equals(mRequestFocusContentUri)) {
 											mInputProcessor.setCurrentSelectedSlot(i);
-											mRequestFocusContentUri = null;
 											break;
 										}
 									}
 								}
 							}
 						}
+						mRequestFocusContentUri = null;
 					}
 				}
 			} finally {
@@ -726,8 +724,8 @@
 	@Override
 	public void onSurfaceCreated(RenderView view, GL11 gl) {
 		sDisplayList.clear();
-		sHud.clear();
-		sHud.reset();
+		mHud.clear();
+		mHud.reset();
 		GridDrawables.sStringTextureTable.clear();
 		mDrawables.onSurfaceCreated(view, gl);
 		mBackground.clear();
@@ -780,8 +778,8 @@
 
 	public void renderBlended(RenderView view, GL11 gl) {
 		// We draw the placeholder for all visible slots.
-		if (sHud != null && mDrawManager != null) {
-			mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, sHud.getMode(), mTimeElapsedSinceStackViewReady,
+		if (mHud != null && mDrawManager != null) {
+			mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, mHud.getMode(), mTimeElapsedSinceStackViewReady,
 			        mTimeElapsedSinceGridViewReady, sBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
 			                || mMediaFeed.isLoading());
 		}
@@ -857,7 +855,7 @@
 			mFeedChanged = true;
 			forceRecomputeVisibleRange();
 			if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
-				sHud.setFeed(feed, mState, needsLayout);
+				mHud.setFeed(feed, mState, needsLayout);
 			return;
 		}
 
@@ -865,10 +863,10 @@
 			Thread.yield();
 		}
 		if (mState == STATE_GRID_VIEW) {
-			if (sHud != null) {
+			if (mHud != null) {
 				MediaSet set = feed.getCurrentSet();
 				if (set != null && !mLocationFilter)
-					sHud.getPathBar().changeLabel(set.mNoCountTitleString);
+					mHud.getPathBar().changeLabel(set.mNoCountTitleString);
 			}
 		}
 		DisplayItem[] displayItems = sDisplayItems;
@@ -953,7 +951,7 @@
 		mFeedChanged = true;
 		if (feed != null) {
 			if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
-				sHud.setFeed(feed, mState, needsLayout);
+				mHud.setFeed(feed, mState, needsLayout);
 		}
 		if (mView != null) {
 			mView.requestRender();
@@ -1058,7 +1056,7 @@
 		boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
 		if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
 			endSlideshow();
-			sHud.setAlpha(1.0f);
+			mHud.setAlpha(1.0f);
 		}
 		return retVal;
 	}
@@ -1070,7 +1068,7 @@
 			DisplayItem displayItem = sDisplayItems[index * MAX_ITEMS_PER_SLOT];
 			if (displayItem != null) {
 				MediaItem item = displayItem.mItemRef;
-				sHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
+				mHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
 				if (slotId != Shared.INVALID && slotId <= sCompleteRange.end) {
 					mInputProcessor.setCurrentFocusSlot(slotId);
 					centerCameraForSlot(slotId, convergence);
@@ -1106,7 +1104,7 @@
 
 	public void deselectOrCancelSelectMode() {
 		if (sBucketList.size() == 0) {
-			sHud.cancelSelection();
+			mHud.cancelSelection();
 		} else {
 			sBucketList.clear();
 			updateCountOfSelectedItems();
@@ -1114,7 +1112,7 @@
 	}
 
 	public void deselectAll() {
-		sHud.cancelSelection();
+		mHud.cancelSelection();
 		sBucketList.clear();
 		updateCountOfSelectedItems();
 	}
@@ -1141,11 +1139,11 @@
 					deselectAll();
 			}
 		}
-		sHud.computeBottomMenu();
+		mHud.computeBottomMenu();
 	}
 
 	private void updateCountOfSelectedItems() {
-		sHud.updateNumItemsSelected(sBucketList.size());
+		mHud.updateNumItemsSelected(sBucketList.size());
 	}
 
 	public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
@@ -1244,7 +1242,7 @@
 		mZoomValue = 1.0f;
 		centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
 		mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
-		sHud.setAlpha(0);
+		mHud.setAlpha(0);
 		PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 		mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
 		mWakeLock.acquire();
@@ -1252,7 +1250,7 @@
 
 	public void enterSelectionMode() {
 		mSlideshowMode = false;
-		sHud.enterSelectionMode();
+		mHud.enterSelectionMode();
 		int currentSlot = mInputProcessor.getCurrentSelectedSlot();
 		if (currentSlot == Shared.INVALID) {
 			currentSlot = mInputProcessor.getCurrentFocusSlot();
@@ -1275,7 +1273,7 @@
 		if (mZoomValue > 6.0f) {
 			mZoomValue = 6.0f;
 		}
-		sHud.setAlpha(1.0f);
+		mHud.setAlpha(1.0f);
 		centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
 	}
 
@@ -1289,7 +1287,7 @@
 		if (mZoomValue < 1.0f) {
 			mZoomValue = 1.0f;
 		}
-		sHud.setAlpha(1.0f);
+		mHud.setAlpha(1.0f);
 		centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
 	}
 
@@ -1373,16 +1371,16 @@
 
 	public void setPickIntent(boolean b) {
 		mPickIntent = b;
-		sHud.getPathBar().popLabel();
-		sHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
+		mHud.getPathBar().popLabel();
+		mHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
 		        new Runnable() {
 			        public void run() {
-				        if (sHud.getAlpha() == 1.0f) {
+				        if (mHud.getAlpha() == 1.0f) {
 					        if (!mFeedAboutToChange) {
 						        setState(STATE_MEDIA_SETS);
 					        }
 				        } else {
-					        sHud.setAlpha(1.0f);
+					        mHud.setAlpha(1.0f);
 				        }
 			        }
 		        });
@@ -1398,19 +1396,19 @@
 			mMediaFeed.expandMediaSet(0);
 			setState(STATE_GRID_VIEW);
 			// We need to make sure we haven't pushed the same label twice
-			if (sHud.getPathBar().getNumLevels() == 1) {
-				sHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
+			if (mHud.getPathBar().getNumLevels() == 1) {
+				mHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
 					public void run() {
 						if (mFeedAboutToChange) {
 							return;
 						}
-						if (sHud.getAlpha() == 1.0f) {
+						if (mHud.getAlpha() == 1.0f) {
 							disableLocationFiltering();
 							if (mInputProcessor != null)
 								mInputProcessor.clearSelection();
 							setState(STATE_GRID_VIEW);
 						} else {
-							sHud.setAlpha(1.0f);
+							mHud.setAlpha(1.0f);
 						}
 					}
 				});
diff --git a/src/com/cooliris/media/HudLayer.java b/src/com/cooliris/media/HudLayer.java
index d7db544..433aaa0 100644
--- a/src/com/cooliris/media/HudLayer.java
+++ b/src/com/cooliris/media/HudLayer.java
@@ -29,8 +29,8 @@
     private final ImageButton mTopRightButton = new ImageButton();
     private final ImageButton mZoomInButton = new ImageButton();
     private final ImageButton mZoomOutButton = new ImageButton();
-    private static PathBarLayer sPathBar;
-    private static TimeBar sTimeBar;
+    private PathBarLayer mPathBar;
+    private TimeBar mTimeBar;
     private MenuBar.Menu[] mNormalBottomMenu = null;
     private MenuBar.Menu[] mSingleViewIntentBottomMenu = null;
     private final MenuBar mSelectionMenuBottom;
@@ -103,9 +103,9 @@
 
     HudLayer(Context context) {
         mAlpha = 1.0f;
-        if (sTimeBar == null) {
-            sTimeBar = new TimeBar(context);
-            sPathBar = new PathBarLayer();
+        if (mTimeBar == null) {
+            mTimeBar = new TimeBar(context);
+            mPathBar = new PathBarLayer();
         }
         mTopRightButton.setSize((int) (100 * Gallery.PIXEL_DENSITY), (int) (94 * Gallery.PIXEL_DENSITY));
 
@@ -191,7 +191,7 @@
     public void setContext(Context context) {
         if (mContext != context) {
             mContext = context;
-            sTimeBar.regenerateStringsForContext(context);
+            mTimeBar.regenerateStringsForContext(context);
         }
     }
 
@@ -416,8 +416,8 @@
         final float height = mHeight;
         closeSelectionMenu();
 
-        sTimeBar.setPosition(0f, height - TimeBar.HEIGHT * Gallery.PIXEL_DENSITY);
-        sTimeBar.setSize(width, TimeBar.HEIGHT * Gallery.PIXEL_DENSITY);
+        mTimeBar.setPosition(0f, height - TimeBar.HEIGHT * Gallery.PIXEL_DENSITY);
+        mTimeBar.setSize(width, TimeBar.HEIGHT * Gallery.PIXEL_DENSITY);
         mSelectionMenuTop.setPosition(0f, 0);
         mSelectionMenuTop.setSize(width, MenuBar.HEIGHT * Gallery.PIXEL_DENSITY);
         mSelectionMenuBottom.setPosition(0f, height - MenuBar.HEIGHT * Gallery.PIXEL_DENSITY);
@@ -426,7 +426,7 @@
         mFullscreenMenu.setPosition(0f, height - MenuBar.HEIGHT * Gallery.PIXEL_DENSITY);
         mFullscreenMenu.setSize(width, MenuBar.HEIGHT * Gallery.PIXEL_DENSITY);
 
-        sPathBar.setPosition(0f, -4f * Gallery.PIXEL_DENSITY);
+        mPathBar.setPosition(0f, -4f * Gallery.PIXEL_DENSITY);
         computeSizeForPathbar();
 
         mTopRightButton.setPosition(width - mTopRightButton.getWidth(), 0f);
@@ -438,12 +438,12 @@
         float pathBarWidth = mWidth
                 - ((mGridLayer.getState() == GridLayer.STATE_FULL_SCREEN) ? 32 * Gallery.PIXEL_DENSITY
                         : 120 * Gallery.PIXEL_DENSITY);
-        sPathBar.setSize(pathBarWidth, FloatMath.ceil(39 * Gallery.PIXEL_DENSITY));
-        sPathBar.recomputeComponents();
+        mPathBar.setSize(pathBarWidth, FloatMath.ceil(39 * Gallery.PIXEL_DENSITY));
+        mPathBar.recomputeComponents();
     }
 
     public void setFeed(MediaFeed feed, int state, boolean needsLayout) {
-        sTimeBar.setFeed(feed, state, needsLayout);
+        mTimeBar.setFeed(feed, state, needsLayout);
     }
 
     public void onGridStateChanged() {
@@ -465,11 +465,11 @@
         mZoomOutButton.setHidden(mFullscreenMenu.isHidden());
 
         // Show the time bar in stack and grid states, except in selection mode.
-        sTimeBar.setHidden(fullscreenMode || selectionMode || stackMode);
+        mTimeBar.setHidden(fullscreenMode || selectionMode || stackMode);
         // mTimeBar.setHidden(selectionMode || (state != GridLayer.STATE_TIMELINE && state != GridLayer.STATE_GRID_VIEW));
 
         // Hide the path bar and top-right button in selection mode.
-        sPathBar.setHidden(selectionMode);
+        mPathBar.setHidden(selectionMode);
         mTopRightButton.setHidden(selectionMode || fullscreenMode);
         computeSizeForPathbar();
 
@@ -505,11 +505,11 @@
     }
 
     public TimeBar getTimeBar() {
-        return sTimeBar;
+        return mTimeBar;
     }
 
     public PathBarLayer getPathBar() {
-        return sPathBar;
+        return mPathBar;
     }
 
     public GridLayer getGridLayer() {
@@ -576,11 +576,11 @@
         mTopRightButton.generate(view, lists);
         mZoomInButton.generate(view, lists);
         mZoomOutButton.generate(view, lists);
-        sTimeBar.generate(view, lists);
+        mTimeBar.generate(view, lists);
         mSelectionMenuTop.generate(view, lists);
         mSelectionMenuBottom.generate(view, lists);
         mFullscreenMenu.generate(view, lists);
-        sPathBar.generate(view, lists);
+        mPathBar.generate(view, lists);
         // mLoadingLayer.generate(view, lists);
         mView = view;
     }
@@ -619,7 +619,7 @@
 
     void reset() {
         mLoadingLayer.reset();
-        sTimeBar.regenerateStringsForContext(mContext);
+        mTimeBar.regenerateStringsForContext(mContext);
     }
 
     public void fullscreenSelectionChanged(MediaItem item, int index, int count) {
@@ -633,7 +633,7 @@
         mCachedCaption = item.mCaption;
         mCachedPosition = location;
         mCachedCurrentLabel = location;
-        sPathBar.changeLabel(location);
+        mPathBar.changeLabel(location);
     }
 
     private void updateShareMenu() {
@@ -745,7 +745,7 @@
 
     public void swapFullscreenLabel() {
         mCachedCurrentLabel = (mCachedCurrentLabel == mCachedCaption || mCachedCaption == null) ? mCachedPosition : mCachedCaption;
-        sPathBar.changeLabel(mCachedCurrentLabel);
+        mPathBar.changeLabel(mCachedCurrentLabel);
     }
 
     public void clear() {
diff --git a/src/com/cooliris/media/MediaFeed.java b/src/com/cooliris/media/MediaFeed.java
index b10187a..a72a9c6 100644
--- a/src/com/cooliris/media/MediaFeed.java
+++ b/src/com/cooliris/media/MediaFeed.java
@@ -242,7 +242,9 @@
     }
 
     public void removeMediaSet(MediaSet set) {
-        mMediaSets.remove(set);
+        synchronized (mMediaSets) {
+        	mMediaSets.remove(set);
+        }
         mMediaFeedNeedsToRun = true;
     }
 
@@ -434,7 +436,7 @@
                     }
                     if (expandedSetIndex == Shared.INVALID) {
                         // We purge the sets outside this visibleRange.
-                        int numSets = mMediaSets.size();
+                        int numSets = mediaSets.size();
                         IndexRange visibleRange = mVisibleRange;
                         IndexRange bufferedRange = mBufferedRange;
                         boolean scanMediaSets = true;
@@ -463,7 +465,7 @@
                                 }
                             }
                         }
-                        numSets = mMediaSets.size();
+                        numSets = mediaSets.size();
                         for (int i = 0; i < numSets; ++i) {
                             MediaSet set = mediaSets.get(i);
                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
diff --git a/src/com/cooliris/media/MediaSet.java b/src/com/cooliris/media/MediaSet.java
index 56c3893..b929d73 100644
--- a/src/com/cooliris/media/MediaSet.java
+++ b/src/com/cooliris/media/MediaSet.java
@@ -167,12 +167,12 @@
                 mMaxTimestamp = dateTaken;
             }
         } else if (item.isDateAddedValid()) {
-            long dateTaken = item.mDateAddedInSec * 1000;
-            if (dateTaken < mMinAddedTimestamp) {
-                mMinAddedTimestamp = dateTaken;
+            long dateAdded = item.mDateAddedInSec * 1000;
+            if (dateAdded < mMinAddedTimestamp) {
+                mMinAddedTimestamp = dateAdded;
             }
-            if (dateTaken > mMaxAddedTimestamp) {
-                mMaxAddedTimestamp = dateTaken;
+            if (dateAdded > mMaxAddedTimestamp) {
+                mMaxAddedTimestamp = dateAdded;
             }            
         }
 
diff --git a/src/com/cooliris/media/RenderView.java b/src/com/cooliris/media/RenderView.java
index d7f386e..dada58f 100644
--- a/src/com/cooliris/media/RenderView.java
+++ b/src/com/cooliris/media/RenderView.java
@@ -864,6 +864,8 @@
             return false;
         }
         // Wait for the render thread to process this event.
+        if (mTouchEventQueue.size() > 8 && event.getAction() == MotionEvent.ACTION_MOVE)
+        	return true;
         synchronized (mTouchEventQueue) {
             MotionEvent eventCopy = MotionEvent.obtain(event);
             mTouchEventQueue.addLast(eventCopy);
diff --git a/src/com/cooliris/media/SingleDataSource.java b/src/com/cooliris/media/SingleDataSource.java
index b0ae940..646f3f1 100644
--- a/src/com/cooliris/media/SingleDataSource.java
+++ b/src/com/cooliris/media/SingleDataSource.java
@@ -60,8 +60,12 @@
     public void shutdown() {
 
     }
+    
+    public boolean isSingleImage() {
+    	return mSingleUri;
+    }
 
-    public static boolean isSingleImageMode(String uriString) {
+    private static boolean isSingleImageMode(String uriString) {
         return !uriString.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
                 && !uriString.equals(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
     }