OSDN Git Service

Updates to 3D gallery. Build 1203.
authorDave Sparks <davidsparks@android.com>
Fri, 4 Dec 2009 02:52:47 +0000 (18:52 -0800)
committerDave Sparks <davidsparks@android.com>
Fri, 4 Dec 2009 02:52:47 +0000 (18:52 -0800)
16 files changed:
src.zip [deleted file]
src/com/cooliris/cache/BootReceiver.java
src/com/cooliris/cache/CacheService.java
src/com/cooliris/media/BackgroundLayer.java
src/com/cooliris/media/ConcatenatedDataSource.java
src/com/cooliris/media/DataSource.java
src/com/cooliris/media/FlatLocalDataSource.java [deleted file]
src/com/cooliris/media/Gallery.java
src/com/cooliris/media/GridCameraManager.java
src/com/cooliris/media/GridInputProcessor.java
src/com/cooliris/media/GridLayer.java
src/com/cooliris/media/HudLayer.java
src/com/cooliris/media/LocalDataSource.java
src/com/cooliris/media/MediaBucketList.java
src/com/cooliris/media/PicasaDataSource.java
src/com/cooliris/media/TimeBar.java

diff --git a/src.zip b/src.zip
deleted file mode 100644 (file)
index ddc6415..0000000
Binary files a/src.zip and /dev/null differ
index 311f3da..dae5355 100644 (file)
@@ -1,19 +1,25 @@
 package com.cooliris.cache;
 
-import com.cooliris.media.Gallery;
+import com.cooliris.media.LocalDataSource;
 import com.cooliris.media.SingleDataSource;
 
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Handler;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
 import android.util.Log;
 
 public class BootReceiver extends BroadcastReceiver {
-    private static final String TAG = "BootReceiver"; 
+    private static final String TAG = "BootReceiver";
+    private final Handler mHandler = new Handler();
 
     @Override
-    public void onReceive(Context context, Intent intent) {
+    public void onReceive(final Context context, Intent intent) {
         final String action = intent.getAction();
         Log.i(TAG, "Got intent with action " + action);
         if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
@@ -29,7 +35,21 @@ public class BootReceiver extends BroadcastReceiver {
                 CacheService.markDirty(context);
             }
         } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-            Gallery.NEEDS_REFRESH = true;
+            // We add special listeners for the MediaProvider
+            final Handler handler = mHandler;
+            final ContentObserver localObserver = new ContentObserver(handler) {
+                public void onChange(boolean selfChange) {
+                    if (!LocalDataSource.sObserverActive) {
+                       CacheService.senseDirty(context, null);
+                    }
+                }
+            };
+            // Start listening perpetually.
+            Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+            Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+            ContentResolver cr = context.getContentResolver();
+            cr.registerContentObserver(uriImages, false, localObserver);
+            cr.registerContentObserver(uriVideos, false, localObserver);
         }
     }
 }
index f3e610f..d54f9fa 100644 (file)
@@ -24,6 +24,7 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
+import android.database.MergeCursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -53,1026 +54,1153 @@ import com.cooliris.media.UriTexture;
 import com.cooliris.media.Utils;
 
 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");
-
-    private static final String TAG = "CacheService";
-    private static ImageList sList = null;
-
-    // Wait 2 seconds to start the thumbnailer so that the application can load without any overheads.
-    private static final int THUMBNAILER_WAIT_IN_MS = 2000;
-    private static final int DEFAULT_THUMBNAIL_WIDTH = 128;
-    private static final int DEFAULT_THUMBNAIL_HEIGHT = 96;
-
-    public static final String DEFAULT_IMAGE_SORT_ORDER = Images.ImageColumns.DATE_TAKEN + " ASC, "
-            + Images.ImageColumns.DATE_ADDED + " ASC";
-    public static final String DEFAULT_VIDEO_SORT_ORDER = Video.VideoColumns.DATE_TAKEN + " ASC, " + Video.VideoColumns.DATE_ADDED
-            + " ASC";
-    public static final String DEFAULT_BUCKET_SORT_ORDER = "upper(" + Images.ImageColumns.BUCKET_DISPLAY_NAME + ") ASC";
-
-    // Must preserve order between these indices and the order of the terms in BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
-    // Not using SortedHashMap for efficieny reasons.
-    public static final int BUCKET_ID_INDEX = 0;
-    public static final int BUCKET_NAME_INDEX = 1;
-    public static final String[] BUCKET_PROJECTION_IMAGES = new String[] { Images.ImageColumns.BUCKET_ID,
-            Images.ImageColumns.BUCKET_DISPLAY_NAME };
-
-    public static final String[] BUCKET_PROJECTION_VIDEOS = new String[] { Video.VideoColumns.BUCKET_ID,
-            Video.VideoColumns.BUCKET_DISPLAY_NAME };
-
-    // Must preserve order between these indices and the order of the terms in THUMBNAIL_PROJECTION.
-    public static final int THUMBNAIL_ID_INDEX = 0;
-    public static final int THUMBNAIL_DATE_MODIFIED_INDEX = 1;
-    public static final int THUMBNAIL_DATA_INDEX = 2;
-    public static final int THUMBNAIL_ORIENTATION_INDEX = 2;
-    public static final String[] THUMBNAIL_PROJECTION = new String[] { Images.ImageColumns._ID, Images.ImageColumns.DATE_MODIFIED,
-            Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION };
-
-    // Must preserve order between these indices and the order of the terms in INITIAL_PROJECTION_IMAGES and
-    // INITIAL_PROJECTION_VIDEOS.
-    public static final int MEDIA_ID_INDEX = 0;
-    public static final int MEDIA_CAPTION_INDEX = 1;
-    public static final int MEDIA_MIME_TYPE_INDEX = 2;
-    public static final int MEDIA_LATITUDE_INDEX = 3;
-    public static final int MEDIA_LONGITUDE_INDEX = 4;
-    public static final int MEDIA_DATE_TAKEN_INDEX = 5;
-    public static final int MEDIA_DATE_ADDED_INDEX = 6;
-    public static final int MEDIA_DATE_MODIFIED_INDEX = 7;
-    public static final int MEDIA_DATA_INDEX = 8;
-    public static final int MEDIA_ORIENTATION_OR_DURATION_INDEX = 9;
-    public static final int MEDIA_BUCKET_ID_INDEX = 10;
-    public static final String[] PROJECTION_IMAGES = new String[] { Images.ImageColumns._ID, Images.ImageColumns.TITLE,
-            Images.ImageColumns.MIME_TYPE, Images.ImageColumns.LATITUDE, Images.ImageColumns.LONGITUDE,
-            Images.ImageColumns.DATE_TAKEN, Images.ImageColumns.DATE_ADDED, Images.ImageColumns.DATE_MODIFIED,
-            Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION, Images.ImageColumns.BUCKET_ID };
-
-    private static final String[] PROJECTION_VIDEOS = new String[] { Video.VideoColumns._ID, Video.VideoColumns.TITLE,
-            Video.VideoColumns.MIME_TYPE, Video.VideoColumns.LATITUDE, Video.VideoColumns.LONGITUDE, Video.VideoColumns.DATE_TAKEN,
-            Video.VideoColumns.DATE_ADDED, Video.VideoColumns.DATE_MODIFIED, Video.VideoColumns.DATA, Video.VideoColumns.DURATION,
-            Video.VideoColumns.BUCKET_ID };
-
-    public static final String BASE_CONTENT_STRING_IMAGES = (Images.Media.EXTERNAL_CONTENT_URI).toString() + "/";
-    public static final String BASE_CONTENT_STRING_VIDEOS = (Video.Media.EXTERNAL_CONTENT_URI).toString() + "/";
-    private static final AtomicReference<Thread> CACHE_THREAD = new AtomicReference<Thread>();
-    private static final AtomicReference<Thread> THUMBNAIL_THREAD = new AtomicReference<Thread>();
-
-    // Special indices in the Albumcache.
-    private static final int ALBUM_CACHE_METADATA_INDEX = -1;
-    private static final int ALBUM_CACHE_DIRTY_INDEX = -2;
-    private static final int ALBUM_CACHE_INCOMPLETE_INDEX = -3;
-    private static final int ALBUM_CACHE_DIRTY_BUCKET_INDEX = -4;
-    private static final int ALBUM_CACHE_LOCALE_INDEX = -5;
-
-    private static final DateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
-    private static final DateFormat mAltDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-    private static final byte[] sDummyData = new byte[] { 1 };
-    private static boolean QUEUE_DIRTY_SET;
-    private static boolean QUEUE_DIRTY_ALL;
-
-    public static final String getCachePath(final String subFolderName) {
-        return Environment.getExternalStorageDirectory() + "/Android/data/com.cooliris.media/cache/" + subFolderName;
-    }
-
-    public static final void startCache(final Context context, final boolean checkthumbnails) {
-        final Locale locale = getLocaleForAlbumCache();
-        final Locale defaultLocale = Locale.getDefault();
-        if (locale == null || !locale.equals(defaultLocale)) {
-            sAlbumCache.deleteAll();
-            putLocaleForAlbumCache(defaultLocale);
-        }
-        final Intent intent = new Intent(ACTION_CACHE, null, context, CacheService.class);
-        intent.putExtra("checkthumbnails", checkthumbnails);
-        context.startService(intent);
-    }
-
-    public static final boolean isCacheReady(final boolean onlyMediaSets) {
-        if (onlyMediaSets) {
-            return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null);
-        } else {
-            return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache
-                    .get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
-        }
-    }
-
-    public static final boolean isCacheReady(final long setId) {
-        final boolean isReady = (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null
-                && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache.get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
-        if (!isReady) {
-            return isReady;
-        }
-        // Also, we need to check if this setId is dirty.
-        final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
-        if (existingData != null && existingData.length > 0) {
-            final long[] ids = toLongArray(existingData);
-            final int numIds = ids.length;
-            for (int i = 0; i < numIds; ++i) {
-                if (ids[i] == setId) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    public static final boolean isPresentInCache(final long setId) {
-        return sAlbumCache.get(setId, 0) != null;
-    }
-
-    public static final void markDirty(final Context context) {
-        sList = null;
-        sAlbumCache.put(ALBUM_CACHE_DIRTY_INDEX, sDummyData);
-        if (CACHE_THREAD.get() == null) {
-            restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
-                public void run() {
-                    refresh(context);
-                }
-            });
-        } else {
-            QUEUE_DIRTY_ALL = true;
-        }
-    }
-
-    public static final void markDirtyImmediate(final long id) {
-        sList = null;
-        byte[] data = longToByteArray(id);
-        final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
-        if (existingData != null && existingData.length > 0) {
-            final long[] ids = toLongArray(existingData);
-            final int numIds = ids.length;
-            for (int i = 0; i < numIds; ++i) {
-                if (ids[i] == id) {
-                    return;
-                }
-            }
-            // Add this to the existing keys and concatenate the byte arrays.
-            data = concat(data, existingData);
-        }
-        sAlbumCache.put(ALBUM_CACHE_DIRTY_BUCKET_INDEX, data);
-    }
-
-    public static final void markDirty(final Context context, final long id) {
-        markDirtyImmediate(id);
-        if (CACHE_THREAD.get() == null) {
-            restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
-                public void run() {
-                    refreshDirtySets(context);
-                }
-            });
-        } else {
-            QUEUE_DIRTY_SET = true;
-        }
-    }
-
-    public static final boolean setHasItems(final ContentResolver cr, final long setId) {
-        final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
-        final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
-        final StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + "=" + setId);
-        final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, whereString.toString(), null, null);
-        if (cursorImages != null && cursorImages.getCount() > 0) {
-            cursorImages.close();
-            return true;
-        }
-        final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, whereString.toString(), null, null);
-        if (cursorVideos != null && cursorVideos.getCount() > 0) {
-            cursorVideos.close();
-            return true;
-        }
-        return false;
-    }
-
-    public static final void loadMediaSets(final MediaFeed feed, final DataSource source, final boolean includeImages,
-            final boolean includeVideos) {
-        int timeElapsed = 0;
-        while (!isCacheReady(true) && timeElapsed < 10000) {
-            try {
-                Thread.sleep(300);
-            } catch (InterruptedException e) {
-                return;
-            }
-            timeElapsed += 300;
-        }
-        final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
-        if (albumData != null && albumData.length > 0) {
-            final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
-            try {
-                final int numAlbums = dis.readInt();
-                Log.i(TAG, "Loading " + numAlbums + " albums.");
-                for (int i = 0; i < numAlbums; ++i) {
-                    final long setId = dis.readLong();
-                    final String name = Utils.readUTF(dis);
-                    final boolean hasImages = dis.readBoolean();
-                    final boolean hasVideos = dis.readBoolean();
-                    MediaSet mediaSet = feed.getMediaSet(setId);
-                    if (mediaSet == null) {
-                        mediaSet = feed.addMediaSet(setId, source);
-                    }
-                    if ((includeImages && hasImages) || (includeVideos && hasVideos)) {
-                        mediaSet.mName = name;
-                        mediaSet.mHasImages = hasImages;
-                        mediaSet.mHasVideos = hasVideos;
-                        mediaSet.mPicasaAlbumId = Shared.INVALID;
-                        mediaSet.generateTitle(true);
-                    }
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Error loading albums.");
-                sAlbumCache.deleteAll();
-                putLocaleForAlbumCache(Locale.getDefault());
-            }
-        } else {
-            Log.d(TAG, "No albums found.");
-        }
-    }
-
-    public static final void loadMediaSet(final MediaFeed feed, final DataSource source, final long bucketId) {
-        int timeElapsed = 0;
-        while (!isCacheReady(false) && timeElapsed < 10000) {
-            try {
-                Thread.sleep(300);
-            } catch (InterruptedException e) {
-                return;
-            }
-            timeElapsed += 300;
-        }
-        final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
-        if (albumData != null && albumData.length > 0) {
-            DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
-            try {
-                final int numAlbums = dis.readInt();
-                for (int i = 0; i < numAlbums; ++i) {
-                    final long setId = dis.readLong();
-                    MediaSet mediaSet = null;
-                    if (setId == bucketId) {
-                        mediaSet = feed.getMediaSet(setId);
-                        if (mediaSet == null) {
-                            mediaSet = feed.addMediaSet(setId, source);
-                        }
-                    } else {
-                        mediaSet = new MediaSet();
-                    }
-                    mediaSet.mName = Utils.readUTF(dis);
-                    if (setId == bucketId) {
-                        mediaSet.mPicasaAlbumId = Shared.INVALID;
-                        mediaSet.generateTitle(true);
-                        return;
-                    }
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Error finding album " + bucketId);
-                sAlbumCache.deleteAll();
-                putLocaleForAlbumCache(Locale.getDefault());
-            }
-        } else {
-            Log.d(TAG, "No album found for album id " + bucketId);
-        }
-    }
-
-    public static final void loadMediaItemsIntoMediaFeed(final MediaFeed feed, final MediaSet set, final int rangeStart,
-            final int rangeEnd, final boolean includeImages, final boolean includeVideos) {
-        int timeElapsed = 0;
-        byte[] albumData = null;
-        while (!isCacheReady(set.mId) && timeElapsed < 30000) {
-            try {
-                Thread.sleep(300);
-            } catch (InterruptedException e) {
-                return;
-            }
-            timeElapsed += 300;
-        }
-        albumData = sAlbumCache.get(set.mId, 0);
-        if (albumData != null && set.mNumItemsLoaded < set.getNumExpectedItems()) {
-            final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
-            try {
-                final int numItems = dis.readInt();
-                Log.i(TAG, "Loading Set Id " + set.mId + " with " + numItems + " items.");
-                set.setNumExpectedItems(numItems);
-                set.mMinTimestamp = dis.readLong();
-                set.mMaxTimestamp = dis.readLong();
-                for (int i = 0; i < numItems; ++i) {
-                    final MediaItem item = new MediaItem();
-                    // Must preserve order with method that writes to cache.
-                    item.mId = dis.readLong();
-                    item.mCaption = Utils.readUTF(dis);
-                    item.mMimeType = Utils.readUTF(dis);
-                    item.setMediaType(dis.readInt());
-                    item.mLatitude = dis.readDouble();
-                    item.mLongitude = dis.readDouble();
-                    item.mDateTakenInMs = dis.readLong();
-                    item.mTriedRetrievingExifDateTaken = dis.readBoolean();
-                    item.mDateAddedInSec = dis.readLong();
-                    item.mDateModifiedInSec = dis.readLong();
-                    item.mDurationInSec = dis.readInt();
-                    item.mRotation = (float) dis.readInt();
-                    item.mFilePath = Utils.readUTF(dis);
-                    int itemMediaType = item.getMediaType();
-                    if ((itemMediaType == MediaItem.MEDIA_TYPE_IMAGE && includeImages)
-                            || (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO && includeVideos)) {
-                        String baseUri = (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) ? BASE_CONTENT_STRING_IMAGES
-                                : BASE_CONTENT_STRING_VIDEOS;
-                        item.mContentUri = baseUri + item.mId;
-                        feed.addItemToMediaSet(item, set);
-                    }
-                }
-                dis.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Error loading items for album " + set.mName);
-                sAlbumCache.deleteAll();
-                putLocaleForAlbumCache(Locale.getDefault());
-            }
-        } else {
-            Log.d(TAG, "No items found for album " + set.mName);
-        }
-        set.updateNumExpectedItems();
-        set.generateTitle(true);
-    }
-
-    public static final void populateVideoItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
-            final String baseUri) {
-        item.setMediaType(MediaItem.MEDIA_TYPE_VIDEO);
-        populateMediaItemFromCursor(item, cr, cursor, baseUri);
-    }
-
-    public static final void populateMediaItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
-            final String baseUri) {
-        item.mId = cursor.getLong(CacheService.MEDIA_ID_INDEX);
-        // item.mCaption = cursor.getString(CacheService.MEDIA_CAPTION_INDEX);
-        item.mMimeType = cursor.getString(CacheService.MEDIA_MIME_TYPE_INDEX);
-        item.mLatitude = cursor.getDouble(CacheService.MEDIA_LATITUDE_INDEX);
-        item.mLongitude = cursor.getDouble(CacheService.MEDIA_LONGITUDE_INDEX);
-        item.mDateTakenInMs = cursor.getLong(CacheService.MEDIA_DATE_TAKEN_INDEX);
-        item.mDateAddedInSec = cursor.getLong(CacheService.MEDIA_DATE_ADDED_INDEX);
-        item.mDateModifiedInSec = cursor.getLong(CacheService.MEDIA_DATE_MODIFIED_INDEX);
-        if (item.mDateTakenInMs == item.mDateModifiedInSec) {
-            item.mDateTakenInMs = item.mDateModifiedInSec * 1000;
-        }
-        item.mFilePath = cursor.getString(CacheService.MEDIA_DATA_INDEX);
-        if (baseUri != null)
-            item.mContentUri = baseUri + item.mId;
-        final int itemMediaType = item.getMediaType();
-        // Check to see if a new date taken is available.
-        final long dateTaken = fetchDateTaken(item);
-        if (dateTaken != -1L && item.mContentUri != null) {
-            item.mDateTakenInMs = dateTaken;
-            final ContentValues values = new ContentValues();
-            if (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
-                values.put(Video.VideoColumns.DATE_TAKEN, item.mDateTakenInMs);
-            } else {
-                values.put(Images.ImageColumns.DATE_TAKEN, item.mDateTakenInMs);
-            }
-            cr.update(Uri.parse(item.mContentUri), values, null, null);
-        }
-
-        final int orientationDurationValue = cursor.getInt(CacheService.MEDIA_ORIENTATION_OR_DURATION_INDEX);
-        if (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
-            item.mRotation = orientationDurationValue;
-        } else {
-            item.mDurationInSec = orientationDurationValue;
-        }
-    }
-
-    // Returns -1 if we failed to examine EXIF information or EXIF parsing failed.
-    public static final long fetchDateTaken(final MediaItem item) {
-        if (!item.isDateTakenValid() && !item.mTriedRetrievingExifDateTaken
-                && (item.mFilePath.endsWith(".jpg") || item.mFilePath.endsWith(".jpeg"))) {
-            try {
-                Log.i(TAG, "Parsing date taken from exif");
-                final ExifInterface exif = new ExifInterface(item.mFilePath);
-                final String dateTakenStr = exif.getAttribute(ExifInterface.TAG_DATETIME);
-                if (dateTakenStr != null) {
-                    try {
-                        final Date dateTaken = mDateFormat.parse(dateTakenStr);
-                        return dateTaken.getTime();
-                    } catch (ParseException pe) {
-                        try {
-                            final Date dateTaken = mAltDateFormat.parse(dateTakenStr);
-                            return dateTaken.getTime();
-                        } catch (ParseException pe2) {
-                            Log.i(TAG, "Unable to parse date out of string - " + dateTakenStr);
-                        }
-                    }
-                }
-            } catch (Exception e) {
-                Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
-            }
-
-            // Ensures that we only try retrieving EXIF date taken once.
-            item.mTriedRetrievingExifDateTaken = true;
-        }
-        return -1L;
-    }
-
-    public static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
-            final long timestamp) {
-        final DiskCache thumbnailCache = (isVideo) ? LocalDataSource.sThumbnailCacheVideo : LocalDataSource.sThumbnailCache;
-        return queryThumbnail(context, thumbId, origId, isVideo, thumbnailCache, timestamp);
-    }
-
-    public static final ImageList getImageList(final Context context) {
-        if (sList != null)
-            return sList;
-        ImageList list = new ImageList();
-        final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
-        final ContentResolver cr = context.getContentResolver();
-        final Cursor cursorImages = cr.query(uriImages, THUMBNAIL_PROJECTION, null, null, null);
-        if (cursorImages != null && cursorImages.moveToFirst()) {
-            final int size = cursorImages.getCount();
-            final long[] ids = new long[size];
-            final long[] thumbnailIds = new long[size];
-            final long[] timestamp = new long[size];
-            final int[] orientation = new int[size];
-            int ctr = 0;
-            do {
-                if (Thread.interrupted()) {
-                    break;
-                }
-                ids[ctr] = cursorImages.getLong(THUMBNAIL_ID_INDEX);
-                timestamp[ctr] = cursorImages.getLong(THUMBNAIL_DATE_MODIFIED_INDEX);
-                thumbnailIds[ctr] = Utils.Crc64Long(cursorImages.getString(THUMBNAIL_DATA_INDEX));
-                orientation[ctr] = cursorImages.getInt(THUMBNAIL_ORIENTATION_INDEX);
-                ++ctr;
-            } while (cursorImages.moveToNext());
-            cursorImages.close();
-            list.ids = ids;
-            list.thumbids = thumbnailIds;
-            list.timestamp = timestamp;
-            list.orientation = orientation;
-        }
-        if (sList == null) {
-            sList = list;
-        }
-        return list;
-    }
-
-    private static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
-            final DiskCache thumbnailCache, final long timestamp) {
-        if (!((Gallery) context).isPaused()) {
-            final Thread thumbnailThread = THUMBNAIL_THREAD.getAndSet(null);
-            if (thumbnailThread != null) {
-                thumbnailThread.interrupt();
-            }
-        }
-        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;
-    }
-
-    private static final void buildThumbnails(final Context context) {
-        Log.i(TAG, "Preparing DiskCache for all thumbnails.");
-        ImageList list = getImageList(context);
-        final int size = (list.ids == null) ? 0 : list.ids.length;
-        final long[] ids = list.ids;
-        final long[] timestamp = list.timestamp;
-        final long[] thumbnailIds = list.thumbids;
-        final DiskCache thumbnailCache = LocalDataSource.sThumbnailCache;
-        for (int i = 0; i < size; ++i) {
-            if (Thread.interrupted()) {
-                return;
-            }
-            final long id = ids[i];
-            final long timeModifiedInSec = timestamp[i];
-            final long thumbnailId = thumbnailIds[i];
-            if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
-                buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
-                        DEFAULT_THUMBNAIL_HEIGHT);
-            }
-        }
-        Log.i(TAG, "DiskCache ready for all thumbnails.");
-    }
-
-    private static final byte[] buildThumbnailForId(final Context context, final DiskCache thumbnailCache, final long thumbId,
-            final long origId, final boolean isVideo, final int thumbnailWidth, final int thumbnailHeight) {
-        if (origId == Shared.INVALID) {
-            return null;
-        }
-        try {
-            Bitmap bitmap = null;
-            Thread.sleep(1);
-            if (!isVideo) {
-                final String uriString = BASE_CONTENT_STRING_IMAGES + origId;
-                UriTexture.invalidateCache(thumbId, 1024);
-                try {
-                    bitmap = UriTexture.createFromUri(context, uriString, 1024, 1024, thumbId, null);
-                } catch (IOException e) {
-                    return null;
-                } catch (URISyntaxException e) {
-                    return null;
-                }
-            } else {
-                Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                new Thread() {
-                    public void run() {
-                        try {
-                            Thread.sleep(5000);
-                        } catch (InterruptedException e) {
-                            ;
-                        }
-                        try {
-                            MediaStore.Video.Thumbnails.cancelThumbnailRequest(context.getContentResolver(), origId);
-                        } catch (Exception e) {
-                            ;
-                        }
-                    }
-                }.start();
-                bitmap = MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), origId,
-                        MediaStore.Video.Thumbnails.MICRO_KIND, null);
-            }
-            if (bitmap == null) {
-                return null;
-            }
-            final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight);
-            return retVal;
-        } catch (InterruptedException e) {
-            return null;
-        }
-    }
-
-    public static final byte[] writeBitmapToCache(final DiskCache thumbnailCache, final long thumbId, final long origId,
-            final Bitmap bitmap, final int thumbnailWidth, final int thumbnailHeight) {
-        final int width = bitmap.getWidth();
-        final int height = bitmap.getHeight();
-        // Detect faces to find the focal point, otherwise fall back to the image center.
-        int focusX = width / 2;
-        int focusY = height / 2;
-        // We have commented out face detection since it slows down the generation of the thumbnail and screennail.
-
-        // final FaceDetector faceDetector = new FaceDetector(width, height, 1);
-        // final FaceDetector.Face[] faces = new FaceDetector.Face[1];
-        // final int numFaces = faceDetector.findFaces(bitmap, faces);
-        // if (numFaces > 0 && faces[0].confidence() >= FaceDetector.Face.CONFIDENCE_THRESHOLD) {
-        // final PointF midPoint = new PointF();
-        // faces[0].getMidPoint(midPoint);
-        // focusX = (int) midPoint.x;
-        // focusY = (int) midPoint.y;
-        // }
-
-        // Crop to thumbnail aspect ratio biased towards the focus point.
-        int cropX;
-        int cropY;
-        int cropWidth;
-        int cropHeight;
-        float scaleFactor;
-        if (thumbnailWidth * height < thumbnailHeight * width) {
-            // Vertically constrained.
-            cropWidth = thumbnailWidth * height / thumbnailHeight;
-            cropX = Math.max(0, Math.min(focusX - cropWidth / 2, width - cropWidth));
-            cropY = 0;
-            cropHeight = height;
-            scaleFactor = (float) thumbnailHeight / height;
-        } else {
-            // Horizontally constrained.
-            cropHeight = thumbnailHeight * width / thumbnailWidth;
-            cropY = Math.max(0, Math.min(focusY - cropHeight / 2, height - cropHeight));
-            cropX = 0;
-            cropWidth = width;
-            scaleFactor = (float) thumbnailWidth / width;
-        }
-        final Bitmap finalBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.RGB_565);
-        final Canvas canvas = new Canvas(finalBitmap);
-        final Paint paint = new Paint();
-        paint.setFilterBitmap(true);
-        canvas.drawColor(0);
-        canvas.drawBitmap(bitmap, new Rect(cropX, cropY, cropX + cropWidth, cropY + cropHeight), new Rect(0, 0, thumbnailWidth,
-                thumbnailHeight), paint);
-        bitmap.recycle();
-
-        // Store (long thumbnailId, short focusX, short focusY, JPEG data).
-        final ByteArrayOutputStream cacheOutput = new ByteArrayOutputStream(16384);
-        final DataOutputStream dataOutput = new DataOutputStream(cacheOutput);
-        byte[] retVal = null;
-        try {
-            dataOutput.writeLong(origId);
-            dataOutput.writeShort((int) ((focusX - cropX) * scaleFactor));
-            dataOutput.writeShort((int) ((focusY - cropY) * scaleFactor));
-            dataOutput.flush();
-            finalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, cacheOutput);
-            retVal = cacheOutput.toByteArray();
-            synchronized (thumbnailCache) {
-                thumbnailCache.put(thumbId, retVal);
-            }
-            cacheOutput.close();
-        } catch (Exception e) {
-            ;
-        }
-        return retVal;
-    }
-
-    public CacheService() {
-        super("CacheService");
-    }
-
-    @Override
-    protected void onHandleIntent(final Intent intent) {
-        Log.i(TAG, "Starting CacheService");
-        if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) {
-            sAlbumCache.deleteAll();
-            putLocaleForAlbumCache(Locale.getDefault());
-        }
-        Locale locale = getLocaleForAlbumCache();
-        if (locale != null && locale.equals(Locale.getDefault())) {
-            // The cache is in the same locale as the system locale.
-            if (!isCacheReady(false)) {
-                // The albums and their items have not yet been cached, we need to run the service.
-                startNewCacheThread();
-            } else {
-                startNewCacheThreadForDirtySets();
-            }
-        } else {
-            // The locale has changed, we need to regenerate the strings.
-            sAlbumCache.deleteAll();
-            putLocaleForAlbumCache(Locale.getDefault());
-            startNewCacheThread();
-        }
-        if (intent.getBooleanExtra("checkthumbnails", false)) {
-            startNewThumbnailThread(this);
-        } else {
-            final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null);
-            if (existingThread != null) {
-                existingThread.interrupt();
-            }
-        }
-    }
-
-    private static final void putLocaleForAlbumCache(final Locale locale) {
-        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        final DataOutputStream dos = new DataOutputStream(bos);
-        try {
-            Utils.writeUTF(dos, locale.getCountry());
-            Utils.writeUTF(dos, locale.getLanguage());
-            Utils.writeUTF(dos, locale.getVariant());
-            dos.flush();
-            bos.flush();
-            final byte[] data = bos.toByteArray();
-            sAlbumCache.put(ALBUM_CACHE_LOCALE_INDEX, data);
-            sAlbumCache.flush();
-            dos.close();
-            bos.close();
-        } catch (IOException e) {
-            // Could not write locale to cache.
-            Log.i(TAG, "Error writing locale to cache.");
-            ;
-        }
-    }
-
-    private static final Locale getLocaleForAlbumCache() {
-        final byte[] data = sAlbumCache.get(ALBUM_CACHE_LOCALE_INDEX, 0);
-        if (data != null && data.length > 0) {
-            ByteArrayInputStream bis = new ByteArrayInputStream(data);
-            DataInputStream dis = new DataInputStream(bis);
-            try {
-                String country = Utils.readUTF(dis);
-                if (country == null)
-                    country = "";
-                String language = Utils.readUTF(dis);
-                if (language == null)
-                    language = "";
-                String variant = Utils.readUTF(dis);
-                if (variant == null)
-                    variant = "";
-                final Locale locale = new Locale(language, country, variant);
-                dis.close();
-                bis.close();
-                return locale;
-            } catch (IOException e) {
-                // Could not read locale in cache.
-                Log.i(TAG, "Error reading locale from cache.");
-                return null;
-            }
-        }
-        return null;
-    }
-
-    private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) {
-        // Create a new thread.
-        final Thread newThread = new Thread() {
-            public void run() {
-                try {
-                    action.run();
-                } finally {
-                    threadRef.compareAndSet(this, null);
-                }
-            }
-        };
-        newThread.setName(name);
-        newThread.start();
-
-        // Interrupt any existing thread.
-        final Thread existingThread = threadRef.getAndSet(newThread);
-        if (existingThread != null) {
-            existingThread.interrupt();
-        }
-    }
-
-    public static final void startNewThumbnailThread(final Context context) {
-        restartThread(THUMBNAIL_THREAD, "ThumbnailRefresh", new Runnable() {
-            public void run() {
-                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                try {
-                    // It is an optimization to prevent the thumbnailer from running while the application loads
-                    Thread.sleep(THUMBNAILER_WAIT_IN_MS);
-                } catch (InterruptedException e) {
-                    return;
-                }
-                CacheService.buildThumbnails(context);
-            }
-        });
-    }
-
-    private void startNewCacheThread() {
-        restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
-            public void run() {
-                refresh(CacheService.this);
-            }
-        });
-    }
-
-    private void startNewCacheThreadForDirtySets() {
-        restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
-            public void run() {
-                refreshDirtySets(CacheService.this);
-            }
-        });
-    }
-
-    private static final byte[] concat(final byte[] A, final byte[] B) {
-        final byte[] C = (byte[]) new byte[A.length + B.length];
-        System.arraycopy(A, 0, C, 0, A.length);
-        System.arraycopy(B, 0, C, A.length, B.length);
-        return C;
-    }
-
-    private static final long[] toLongArray(final byte[] data) {
-        final ByteBuffer bBuffer = ByteBuffer.wrap(data);
-        final LongBuffer lBuffer = bBuffer.asLongBuffer();
-        final int numLongs = lBuffer.capacity();
-        final long[] retVal = new long[numLongs];
-        for (int i = 0; i < numLongs; ++i) {
-            retVal[i] = lBuffer.get(i);
-        }
-        return retVal;
-    }
-
-    private static final byte[] longToByteArray(final long l) {
-        final byte[] bArray = new byte[8];
-        final ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
-        final LongBuffer lBuffer = bBuffer.asLongBuffer();
-        lBuffer.put(0, l);
-        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());
-
-        final ArrayList<MediaSet> sets = new ArrayList<MediaSet>();
-        LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>();
-        Log.i(TAG, "Building albums.");
-        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 ContentResolver cr = context.getContentResolver();
-
-        final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);
-        final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);
-        Cursor[] cursors = new Cursor[2];
-        cursors[0] = cursorImages;
-        cursors[1] = cursorVideos;
-        final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true);
-        try {
-            if (sortCursor != null && sortCursor.moveToFirst()) {
-                sets.ensureCapacity(sortCursor.getCount());
-                acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount());
-                MediaSet cameraSet = new MediaSet();
-                cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;
-                cameraSet.mName = context.getResources().getString(R.string.camera);
-                sets.add(cameraSet);
-                acceleratedSets.put(cameraSet.mId, cameraSet);
-                do {
-                    if (Thread.interrupted()) {
-                        return;
-                    }
-                    long setId = sortCursor.getLong(BUCKET_ID_INDEX);
-                    MediaSet mediaSet = findSet(setId, acceleratedSets);
-                    if (mediaSet == null) {
-                        mediaSet = new MediaSet();
-                        mediaSet.mId = setId;
-                        mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX);
-                        sets.add(mediaSet);
-                        acceleratedSets.put(setId, mediaSet);
-                    }
-                    mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0);
-                    mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1);
-                } while (sortCursor.moveToNext());
-                sortCursor.close();
-            }
-        } finally {
-            if (sortCursor != null)
-                sortCursor.close();
-        }
-
-        sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData);
-        writeSetsToCache(sets);
-        Log.i(TAG, "Done building albums.");
-        // 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);
-        if (QUEUE_DIRTY_ALL) {
-            QUEUE_DIRTY_ALL = false;
-            refresh(context);
-        }
-    }
-
-    private final static void refreshDirtySets(final Context context) {
-        final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
-        if (existingData != null && existingData.length > 0) {
-            final long[] ids = toLongArray(existingData);
-            final int numIds = ids.length;
-            if (numIds > 0) {
-                final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(numIds);
-                final LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(numIds);
-                for (int i = 0; i < numIds; ++i) {
-                    final MediaSet set = new MediaSet();
-                    set.mId = ids[i];
-                    sets.add(set);
-                    acceleratedSets.put(set.mId, set);
-                }
-                Log.i(TAG, "Refreshing dirty albums");
-                populateMediaItemsForSets(context, sets, acceleratedSets, true);
-            }
-        }
-        if (QUEUE_DIRTY_SET) {
-            QUEUE_DIRTY_SET = false;
-            refreshDirtySets(context);
-        } else {
-            sAlbumCache.delete(ALBUM_CACHE_DIRTY_BUCKET_INDEX);
-        }
-    }
-
-    private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets,
-            final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) {
-        if (sets == null || sets.size() == 0 || Thread.interrupted()) {
-            return;
-        }
-        Log.i(TAG, "Building items.");
-        final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
-        final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
-        final ContentResolver cr = context.getContentResolver();
-
-        String whereClause = null;
-        if (useWhere) {
-            int numSets = sets.size();
-            StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in (");
-            for (int i = 0; i < numSets; ++i) {
-                whereString.append(sets.get(i).mId);
-                if (i != numSets - 1) {
-                    whereString.append(",");
-                }
-            }
-            whereString.append(")");
-            whereClause = whereString.toString();
-            Log.i(TAG, "Updating dirty albums where " + whereClause);
-        }
-
-        final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER);
-        final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER);
-        final Cursor[] cursors = new Cursor[2];
-        cursors[0] = cursorImages;
-        cursors[1] = cursorVideos;
-        final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true);
-        if (Thread.interrupted()) {
-            return;
-        }
-        try {
-            if (sortCursor != null && sortCursor.moveToFirst()) {
-                final int count = sortCursor.getCount();
-                final int numSets = sets.size();
-                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 {
-                    if (Thread.interrupted()) {
-                        return;
-                    }
-                    final MediaItem item = new MediaItem();
-                    final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1);
-                    if (isVideo) {
-                        populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);
-                    } else {
-                        populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);
-                    }
-                    final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX);
-                    final MediaSet set = findSet(setId, acceleratedSets);
-                    if (set != null) {
-                        set.getItems().add(item);
-                    }
-                } while (sortCursor.moveToNext());
-            }
-        } finally {
-            if (sortCursor != null) sortCursor.close();
-        }
-        if (sets.size() > 0) {
-            writeItemsToCache(sets);
-            Log.i(TAG, "Done building items.");
-        }
-    }
-
-    private static final void writeSetsToCache(final ArrayList<MediaSet> sets) {
-        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        final int numSets = sets.size();
-        final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
-        try {
-            dos.writeInt(numSets);
-            for (int i = 0; i < numSets; ++i) {
-                if (Thread.interrupted()) {
-                    return;
-                }
-                final MediaSet set = sets.get(i);
-                dos.writeLong(set.mId);
-                Utils.writeUTF(dos, set.mName);
-                dos.writeBoolean(set.mHasImages);
-                dos.writeBoolean(set.mHasVideos);
-            }
-            dos.flush();
-            sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray());
-            dos.close();
-            if (numSets == 0) {
-                sAlbumCache.deleteAll();
-                putLocaleForAlbumCache(Locale.getDefault());
-            }
-            sAlbumCache.flush();
-        } catch (IOException e) {
-            Log.e(TAG, "Error writing albums to diskcache.");
-            sAlbumCache.deleteAll();
-            putLocaleForAlbumCache(Locale.getDefault());
-        }
-    }
-
-    private static final void writeItemsToCache(final ArrayList<MediaSet> sets) {
-        final int numSets = sets.size();
-        for (int i = 0; i < numSets; ++i) {
-            if (Thread.interrupted()) {
-                return;
-            }
-            writeItemsForASet(sets.get(i));
-        }
-        sAlbumCache.flush();
-    }
-
-    private static final void writeItemsForASet(final MediaSet set) {
-        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
-        try {
-            final ArrayList<MediaItem> items = set.getItems();
-            final int numItems = items.size();
-            dos.writeInt(numItems);
-            dos.writeLong(set.mMinTimestamp);
-            dos.writeLong(set.mMaxTimestamp);
-            for (int i = 0; i < numItems; ++i) {
-                MediaItem item = items.get(i);
-                if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) {
-                    // Reverse the display order for the camera bucket - want the latest first.
-                    item = items.get(numItems - i - 1);
-                }
-                dos.writeLong(item.mId);
-                Utils.writeUTF(dos, item.mCaption);
-                Utils.writeUTF(dos, item.mMimeType);
-                dos.writeInt(item.getMediaType());
-                dos.writeDouble(item.mLatitude);
-                dos.writeDouble(item.mLongitude);
-                dos.writeLong(item.mDateTakenInMs);
-                dos.writeBoolean(item.mTriedRetrievingExifDateTaken);
-                dos.writeLong(item.mDateAddedInSec);
-                dos.writeLong(item.mDateModifiedInSec);
-                dos.writeInt(item.mDurationInSec);
-                dos.writeInt((int) item.mRotation);
-                Utils.writeUTF(dos, item.mFilePath);
-            }
-            dos.flush();
-            sAlbumCache.put(set.mId, bos.toByteArray());
-            dos.close();
-        } catch (IOException e) {
-            Log.e(TAG, "Error writing to diskcache for set " + set.mName);
-            sAlbumCache.deleteAll();
-            putLocaleForAlbumCache(Locale.getDefault());
-        }
-    }
-
-    private static final MediaSet findSet(final long id, final LongSparseArray<MediaSet> acceleratedTable) {
-        // This is the accelerated lookup table for the MediaSet based on set id.
-        return acceleratedTable.get(id);
-    }
+       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");
+
+       private static final String TAG = "CacheService";
+       private static ImageList sList = null;
+
+       // Wait 2 seconds to start the thumbnailer so that the application can load
+       // without any overheads.
+       private static final int THUMBNAILER_WAIT_IN_MS = 2000;
+       private static final int DEFAULT_THUMBNAIL_WIDTH = 128;
+       private static final int DEFAULT_THUMBNAIL_HEIGHT = 96;
+
+       public static final String DEFAULT_IMAGE_SORT_ORDER = Images.ImageColumns.DATE_TAKEN + " ASC, "
+               + Images.ImageColumns.DATE_ADDED + " ASC";
+       public static final String DEFAULT_VIDEO_SORT_ORDER = Video.VideoColumns.DATE_TAKEN + " ASC, " + Video.VideoColumns.DATE_ADDED
+               + " ASC";
+       public static final String DEFAULT_BUCKET_SORT_ORDER = "upper(" + Images.ImageColumns.BUCKET_DISPLAY_NAME + ") ASC";
+
+       // Must preserve order between these indices and the order of the terms in
+       // BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
+       // Not using SortedHashMap for efficieny reasons.
+       public static final int BUCKET_ID_INDEX = 0;
+       public static final int BUCKET_NAME_INDEX = 1;
+       public static final String[] BUCKET_PROJECTION_IMAGES = new String[] { Images.ImageColumns.BUCKET_ID,
+               Images.ImageColumns.BUCKET_DISPLAY_NAME };
+
+       public static final String[] BUCKET_PROJECTION_VIDEOS = new String[] { Video.VideoColumns.BUCKET_ID,
+               Video.VideoColumns.BUCKET_DISPLAY_NAME };
+
+       // Must preserve order between these indices and the order of the terms in
+       // THUMBNAIL_PROJECTION.
+       public static final int THUMBNAIL_ID_INDEX = 0;
+       public static final int THUMBNAIL_DATE_MODIFIED_INDEX = 1;
+       public static final int THUMBNAIL_DATA_INDEX = 2;
+       public static final int THUMBNAIL_ORIENTATION_INDEX = 2;
+       public static final String[] THUMBNAIL_PROJECTION = new String[] { Images.ImageColumns._ID, Images.ImageColumns.DATE_MODIFIED,
+               Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION };
+
+       public static final String[] SENSE_PROJECTION = new String[] { Images.ImageColumns.BUCKET_ID,
+               "MAX(" + Images.ImageColumns.DATE_ADDED + ")" };
+
+       // Must preserve order between these indices and the order of the terms in
+       // INITIAL_PROJECTION_IMAGES and
+       // INITIAL_PROJECTION_VIDEOS.
+       public static final int MEDIA_ID_INDEX = 0;
+       public static final int MEDIA_CAPTION_INDEX = 1;
+       public static final int MEDIA_MIME_TYPE_INDEX = 2;
+       public static final int MEDIA_LATITUDE_INDEX = 3;
+       public static final int MEDIA_LONGITUDE_INDEX = 4;
+       public static final int MEDIA_DATE_TAKEN_INDEX = 5;
+       public static final int MEDIA_DATE_ADDED_INDEX = 6;
+       public static final int MEDIA_DATE_MODIFIED_INDEX = 7;
+       public static final int MEDIA_DATA_INDEX = 8;
+       public static final int MEDIA_ORIENTATION_OR_DURATION_INDEX = 9;
+       public static final int MEDIA_BUCKET_ID_INDEX = 10;
+       public static final String[] PROJECTION_IMAGES = new String[] { Images.ImageColumns._ID, Images.ImageColumns.TITLE,
+               Images.ImageColumns.MIME_TYPE, Images.ImageColumns.LATITUDE, Images.ImageColumns.LONGITUDE,
+               Images.ImageColumns.DATE_TAKEN, Images.ImageColumns.DATE_ADDED, Images.ImageColumns.DATE_MODIFIED,
+               Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION, Images.ImageColumns.BUCKET_ID };
+
+       private static final String[] PROJECTION_VIDEOS = new String[] { Video.VideoColumns._ID, Video.VideoColumns.TITLE,
+               Video.VideoColumns.MIME_TYPE, Video.VideoColumns.LATITUDE, Video.VideoColumns.LONGITUDE, Video.VideoColumns.DATE_TAKEN,
+               Video.VideoColumns.DATE_ADDED, Video.VideoColumns.DATE_MODIFIED, Video.VideoColumns.DATA, Video.VideoColumns.DURATION,
+               Video.VideoColumns.BUCKET_ID };
+
+       public static final String BASE_CONTENT_STRING_IMAGES = (Images.Media.EXTERNAL_CONTENT_URI).toString() + "/";
+       public static final String BASE_CONTENT_STRING_VIDEOS = (Video.Media.EXTERNAL_CONTENT_URI).toString() + "/";
+       private static final AtomicReference<Thread> CACHE_THREAD = new AtomicReference<Thread>();
+       private static final AtomicReference<Thread> THUMBNAIL_THREAD = new AtomicReference<Thread>();
+
+       // Special indices in the Albumcache.
+       private static final int ALBUM_CACHE_METADATA_INDEX = -1;
+       private static final int ALBUM_CACHE_DIRTY_INDEX = -2;
+       private static final int ALBUM_CACHE_INCOMPLETE_INDEX = -3;
+       private static final int ALBUM_CACHE_DIRTY_BUCKET_INDEX = -4;
+       private static final int ALBUM_CACHE_LOCALE_INDEX = -5;
+
+       private static final DateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
+       private static final DateFormat mAltDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+       private static final byte[] sDummyData = new byte[] { 1 };
+       private static boolean QUEUE_DIRTY_SET;
+       private static boolean QUEUE_DIRTY_ALL;
+       private static boolean QUEUE_DIRTY_SENSE;
+
+       public interface Observer {
+               void onChange(long[] bucketIds);
+       }
+
+       public static final String getCachePath(final String subFolderName) {
+               return Environment.getExternalStorageDirectory() + "/Android/data/com.cooliris.media/cache/" + subFolderName;
+       }
+
+       public static final void startCache(final Context context, final boolean checkthumbnails) {
+               final Locale locale = getLocaleForAlbumCache();
+               final Locale defaultLocale = Locale.getDefault();
+               if (locale == null || !locale.equals(defaultLocale)) {
+                       sAlbumCache.deleteAll();
+                       putLocaleForAlbumCache(defaultLocale);
+               }
+               final Intent intent = new Intent(ACTION_CACHE, null, context, CacheService.class);
+               intent.putExtra("checkthumbnails", checkthumbnails);
+               context.startService(intent);
+       }
+
+       public static final boolean isCacheReady(final boolean onlyMediaSets) {
+               if (onlyMediaSets) {
+                       return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null);
+               } else {
+                       return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache
+                               .get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
+               }
+       }
+
+       public static final boolean isCacheReady(final long setId) {
+               final boolean isReady = (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null
+                       && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache.get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
+               if (!isReady) {
+                       return isReady;
+               }
+               // Also, we need to check if this setId is dirty.
+               final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
+               if (existingData != null && existingData.length > 0) {
+                       final long[] ids = toLongArray(existingData);
+                       final int numIds = ids.length;
+                       for (int i = 0; i < numIds; ++i) {
+                               if (ids[i] == setId) {
+                                       return false;
+                               }
+                       }
+               }
+               return true;
+       }
+
+       public static final boolean isPresentInCache(final long setId) {
+               return sAlbumCache.get(setId, 0) != null;
+       }
+
+       public static final void senseDirty(final Context context, final Observer observer) {
+               if (CACHE_THREAD.get() == null) {
+                       QUEUE_DIRTY_SENSE = false;
+                       QUEUE_DIRTY_ALL = false;
+                       QUEUE_DIRTY_SET = false;
+                       restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
+                               public void run() {
+                                       Log.i(TAG, "Computing dirty sets.");
+                                       long ids[] = computeDirtySets(context);
+                                       if (ids != null && observer != null) {
+                                               observer.onChange(ids);
+                                       }
+                                       if (ids.length > 0) {
+                                               sList = null;
+                                       }
+                                       Log.i(TAG, "Done computing dirty sets for num " + ids.length);
+                               }
+                       });
+               } else {
+                       QUEUE_DIRTY_SENSE = true;
+               }
+       }
+
+       public static final void markDirty(final Context context) {
+               sList = null;
+               sAlbumCache.put(ALBUM_CACHE_DIRTY_INDEX, sDummyData);
+               if (CACHE_THREAD.get() == null) {
+                       QUEUE_DIRTY_SENSE = false;
+                       QUEUE_DIRTY_ALL = false;
+                       QUEUE_DIRTY_SET = false;
+                       restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
+                               public void run() {
+                                       refresh(context);
+                               }
+                       });
+               } else {
+                       QUEUE_DIRTY_ALL = true;
+               }
+       }
+
+       public static final void markDirtyImmediate(final long id) {
+               if (id == Shared.INVALID) {
+                       return;
+               }
+               sList = null;
+               byte[] data = longToByteArray(id);
+               final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
+               if (existingData != null && existingData.length > 0) {
+                       final long[] ids = toLongArray(existingData);
+                       final int numIds = ids.length;
+                       for (int i = 0; i < numIds; ++i) {
+                               if (ids[i] == id) {
+                                       return;
+                               }
+                       }
+                       // Add this to the existing keys and concatenate the byte arrays.
+                       data = concat(data, existingData);
+               }
+               sAlbumCache.put(ALBUM_CACHE_DIRTY_BUCKET_INDEX, data);
+       }
+
+       public static final void markDirty(final Context context, final long id) {
+               markDirtyImmediate(id);
+               if (CACHE_THREAD.get() == null) {
+                       QUEUE_DIRTY_SET = false;
+                       restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
+                               public void run() {
+                                       refreshDirtySets(context);
+                               }
+                       });
+               } else {
+                       QUEUE_DIRTY_SET = true;
+               }
+       }
+
+       public static final boolean setHasItems(final ContentResolver cr, final long setId) {
+               final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+               final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+               final StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + "=" + setId);
+               final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, whereString.toString(), null, null);
+               if (cursorImages != null && cursorImages.getCount() > 0) {
+                       cursorImages.close();
+                       return true;
+               }
+               final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, whereString.toString(), null, null);
+               if (cursorVideos != null && cursorVideos.getCount() > 0) {
+                       cursorVideos.close();
+                       return true;
+               }
+               return false;
+       }
+
+       public static final void loadMediaSets(final MediaFeed feed, final DataSource source, final boolean includeImages,
+               final boolean includeVideos) {
+               int timeElapsed = 0;
+               while (!isCacheReady(true) && timeElapsed < 10000) {
+                       try {
+                               Thread.sleep(300);
+                       } catch (InterruptedException e) {
+                               return;
+                       }
+                       timeElapsed += 300;
+               }
+               final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
+               if (albumData != null && albumData.length > 0) {
+                       final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
+                       try {
+                               final int numAlbums = dis.readInt();
+                               for (int i = 0; i < numAlbums; ++i) {
+                                       final long setId = dis.readLong();
+                                       final String name = Utils.readUTF(dis);
+                                       final boolean hasImages = dis.readBoolean();
+                                       final boolean hasVideos = dis.readBoolean();
+                                       MediaSet mediaSet = feed.getMediaSet(setId);
+                                       if (mediaSet == null) {
+                                               mediaSet = feed.addMediaSet(setId, source);
+                                       }
+                                       if ((includeImages && hasImages) || (includeVideos && hasVideos)) {
+                                               mediaSet.mName = name;
+                                               mediaSet.mHasImages = hasImages;
+                                               mediaSet.mHasVideos = hasVideos;
+                                               mediaSet.mPicasaAlbumId = Shared.INVALID;
+                                               mediaSet.generateTitle(true);
+                                       }
+                               }
+                       } catch (IOException e) {
+                               Log.e(TAG, "Error loading albums.");
+                               sAlbumCache.deleteAll();
+                               putLocaleForAlbumCache(Locale.getDefault());
+                       }
+               } else {
+                       Log.d(TAG, "No albums found.");
+               }
+       }
+
+       public static final void loadMediaSet(final MediaFeed feed, final DataSource source, final long bucketId) {
+               int timeElapsed = 0;
+               while (!isCacheReady(false) && timeElapsed < 10000) {
+                       try {
+                               Thread.sleep(300);
+                       } catch (InterruptedException e) {
+                               return;
+                       }
+                       timeElapsed += 300;
+               }
+               final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
+               if (albumData != null && albumData.length > 0) {
+                       DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
+                       try {
+                               final int numAlbums = dis.readInt();
+                               for (int i = 0; i < numAlbums; ++i) {
+                                       final long setId = dis.readLong();
+                                       MediaSet mediaSet = null;
+                                       if (setId == bucketId) {
+                                               mediaSet = feed.getMediaSet(setId);
+                                               if (mediaSet == null) {
+                                                       mediaSet = feed.addMediaSet(setId, source);
+                                               }
+                                       } else {
+                                               mediaSet = new MediaSet();
+                                       }
+                                       mediaSet.mName = Utils.readUTF(dis);
+                                       if (setId == bucketId) {
+                                               mediaSet.mPicasaAlbumId = Shared.INVALID;
+                                               mediaSet.generateTitle(true);
+                                               return;
+                                       }
+                               }
+                       } catch (IOException e) {
+                               Log.e(TAG, "Error finding album " + bucketId);
+                               sAlbumCache.deleteAll();
+                               putLocaleForAlbumCache(Locale.getDefault());
+                       }
+               } else {
+                       Log.d(TAG, "No album found for album id " + bucketId);
+               }
+       }
+
+       public static final void loadMediaItemsIntoMediaFeed(final MediaFeed feed, final MediaSet set, final int rangeStart,
+               final int rangeEnd, final boolean includeImages, final boolean includeVideos) {
+               int timeElapsed = 0;
+               byte[] albumData = null;
+               while (!isCacheReady(set.mId) && timeElapsed < 30000) {
+                       try {
+                               Thread.sleep(300);
+                       } catch (InterruptedException e) {
+                               return;
+                       }
+                       timeElapsed += 300;
+               }
+               albumData = sAlbumCache.get(set.mId, 0);
+               if (albumData != null && set.mNumItemsLoaded < set.getNumExpectedItems()) {
+                       final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
+                       try {
+                               final int numItems = dis.readInt();
+                               set.setNumExpectedItems(numItems);
+                               set.mMinTimestamp = dis.readLong();
+                               set.mMaxTimestamp = dis.readLong();
+                               for (int i = 0; i < numItems; ++i) {
+                                       final MediaItem item = new MediaItem();
+                                       // Must preserve order with method that writes to cache.
+                                       item.mId = dis.readLong();
+                                       item.mCaption = Utils.readUTF(dis);
+                                       item.mMimeType = Utils.readUTF(dis);
+                                       item.setMediaType(dis.readInt());
+                                       item.mLatitude = dis.readDouble();
+                                       item.mLongitude = dis.readDouble();
+                                       item.mDateTakenInMs = dis.readLong();
+                                       item.mTriedRetrievingExifDateTaken = dis.readBoolean();
+                                       item.mDateAddedInSec = dis.readLong();
+                                       item.mDateModifiedInSec = dis.readLong();
+                                       item.mDurationInSec = dis.readInt();
+                                       item.mRotation = (float) dis.readInt();
+                                       item.mFilePath = Utils.readUTF(dis);
+                                       int itemMediaType = item.getMediaType();
+                                       if ((itemMediaType == MediaItem.MEDIA_TYPE_IMAGE && includeImages)
+                                               || (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO && includeVideos)) {
+                                               String baseUri = (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) ? BASE_CONTENT_STRING_IMAGES
+                                                       : BASE_CONTENT_STRING_VIDEOS;
+                                               item.mContentUri = baseUri + item.mId;
+                                               feed.addItemToMediaSet(item, set);
+                                       }
+                               }
+                               dis.close();
+                       } catch (IOException e) {
+                               Log.e(TAG, "Error loading items for album " + set.mName);
+                               sAlbumCache.deleteAll();
+                               putLocaleForAlbumCache(Locale.getDefault());
+                       }
+               } else {
+                       Log.d(TAG, "No items found for album " + set.mName);
+               }
+               set.updateNumExpectedItems();
+               set.generateTitle(true);
+       }
+
+       public static final void populateVideoItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
+               final String baseUri) {
+               item.setMediaType(MediaItem.MEDIA_TYPE_VIDEO);
+               populateMediaItemFromCursor(item, cr, cursor, baseUri);
+       }
+
+       public static final void populateMediaItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
+               final String baseUri) {
+               item.mId = cursor.getLong(CacheService.MEDIA_ID_INDEX);
+               item.mCaption = cursor.getString(CacheService.MEDIA_CAPTION_INDEX);
+               item.mMimeType = cursor.getString(CacheService.MEDIA_MIME_TYPE_INDEX);
+               item.mLatitude = cursor.getDouble(CacheService.MEDIA_LATITUDE_INDEX);
+               item.mLongitude = cursor.getDouble(CacheService.MEDIA_LONGITUDE_INDEX);
+               item.mDateTakenInMs = cursor.getLong(CacheService.MEDIA_DATE_TAKEN_INDEX);
+               item.mDateAddedInSec = cursor.getLong(CacheService.MEDIA_DATE_ADDED_INDEX);
+               item.mDateModifiedInSec = cursor.getLong(CacheService.MEDIA_DATE_MODIFIED_INDEX);
+               if (item.mDateTakenInMs == item.mDateModifiedInSec) {
+                       item.mDateTakenInMs = item.mDateModifiedInSec * 1000;
+               }
+               item.mFilePath = cursor.getString(CacheService.MEDIA_DATA_INDEX);
+               if (baseUri != null)
+                       item.mContentUri = baseUri + item.mId;
+               final int itemMediaType = item.getMediaType();
+               // Check to see if a new date taken is available.
+               final long dateTaken = fetchDateTaken(item);
+               if (dateTaken != -1L && item.mContentUri != null) {
+                       item.mDateTakenInMs = dateTaken;
+                       final ContentValues values = new ContentValues();
+                       if (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
+                               values.put(Video.VideoColumns.DATE_TAKEN, item.mDateTakenInMs);
+                       } else {
+                               values.put(Images.ImageColumns.DATE_TAKEN, item.mDateTakenInMs);
+                       }
+                       cr.update(Uri.parse(item.mContentUri), values, null, null);
+               }
+
+               final int orientationDurationValue = cursor.getInt(CacheService.MEDIA_ORIENTATION_OR_DURATION_INDEX);
+               if (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
+                       item.mRotation = orientationDurationValue;
+               } else {
+                       item.mDurationInSec = orientationDurationValue;
+               }
+       }
+
+       // Returns -1 if we failed to examine EXIF information or EXIF parsing
+       // failed.
+       public static final long fetchDateTaken(final MediaItem item) {
+               if (!item.isDateTakenValid() && !item.mTriedRetrievingExifDateTaken
+                       && (item.mFilePath.endsWith(".jpg") || item.mFilePath.endsWith(".jpeg"))) {
+                       try {
+                               Log.i(TAG, "Parsing date taken from exif");
+                               final ExifInterface exif = new ExifInterface(item.mFilePath);
+                               final String dateTakenStr = exif.getAttribute(ExifInterface.TAG_DATETIME);
+                               if (dateTakenStr != null) {
+                                       try {
+                                               final Date dateTaken = mDateFormat.parse(dateTakenStr);
+                                               return dateTaken.getTime();
+                                       } catch (ParseException pe) {
+                                               try {
+                                                       final Date dateTaken = mAltDateFormat.parse(dateTakenStr);
+                                                       return dateTaken.getTime();
+                                               } catch (ParseException pe2) {
+                                                       Log.i(TAG, "Unable to parse date out of string - " + dateTakenStr);
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
+                       }
+
+                       // Ensures that we only try retrieving EXIF date taken once.
+                       item.mTriedRetrievingExifDateTaken = true;
+               }
+               return -1L;
+       }
+
+       public static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
+               final long timestamp) {
+               final DiskCache thumbnailCache = (isVideo) ? LocalDataSource.sThumbnailCacheVideo : LocalDataSource.sThumbnailCache;
+               return queryThumbnail(context, thumbId, origId, isVideo, thumbnailCache, timestamp);
+       }
+
+       public static final ImageList getImageList(final Context context) {
+               if (sList != null)
+                       return sList;
+               ImageList list = new ImageList();
+               final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+               final ContentResolver cr = context.getContentResolver();
+               final Cursor cursorImages = cr.query(uriImages, THUMBNAIL_PROJECTION, null, null, null);
+               if (cursorImages != null && cursorImages.moveToFirst()) {
+                       final int size = cursorImages.getCount();
+                       final long[] ids = new long[size];
+                       final long[] thumbnailIds = new long[size];
+                       final long[] timestamp = new long[size];
+                       final int[] orientation = new int[size];
+                       int ctr = 0;
+                       do {
+                               if (Thread.interrupted()) {
+                                       break;
+                               }
+                               ids[ctr] = cursorImages.getLong(THUMBNAIL_ID_INDEX);
+                               timestamp[ctr] = cursorImages.getLong(THUMBNAIL_DATE_MODIFIED_INDEX);
+                               thumbnailIds[ctr] = Utils.Crc64Long(cursorImages.getString(THUMBNAIL_DATA_INDEX));
+                               orientation[ctr] = cursorImages.getInt(THUMBNAIL_ORIENTATION_INDEX);
+                               ++ctr;
+                       } while (cursorImages.moveToNext());
+                       cursorImages.close();
+                       list.ids = ids;
+                       list.thumbids = thumbnailIds;
+                       list.timestamp = timestamp;
+                       list.orientation = orientation;
+               }
+               if (sList == null) {
+                       sList = list;
+               }
+               return list;
+       }
+
+       private static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
+               final DiskCache thumbnailCache, final long timestamp) {
+               if (!((Gallery) context).isPaused()) {
+                       final Thread thumbnailThread = THUMBNAIL_THREAD.getAndSet(null);
+                       if (thumbnailThread != null) {
+                               thumbnailThread.interrupt();
+                       }
+               }
+               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;
+       }
+
+       private static final void buildThumbnails(final Context context) {
+               Log.i(TAG, "Preparing DiskCache for all thumbnails.");
+               ImageList list = getImageList(context);
+               final int size = (list.ids == null) ? 0 : list.ids.length;
+               final long[] ids = list.ids;
+               final long[] timestamp = list.timestamp;
+               final long[] thumbnailIds = list.thumbids;
+               final DiskCache thumbnailCache = LocalDataSource.sThumbnailCache;
+               for (int i = 0; i < size; ++i) {
+                       if (Thread.interrupted()) {
+                               return;
+                       }
+                       final long id = ids[i];
+                       final long timeModifiedInSec = timestamp[i];
+                       final long thumbnailId = thumbnailIds[i];
+                       if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
+                               buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
+                                       DEFAULT_THUMBNAIL_HEIGHT);
+                       }
+               }
+               Log.i(TAG, "DiskCache ready for all thumbnails.");
+       }
+
+       private static final byte[] buildThumbnailForId(final Context context, final DiskCache thumbnailCache, final long thumbId,
+               final long origId, final boolean isVideo, final int thumbnailWidth, final int thumbnailHeight) {
+               if (origId == Shared.INVALID) {
+                       return null;
+               }
+               try {
+                       Bitmap bitmap = null;
+                       Thread.sleep(1);
+                       if (!isVideo) {
+                               final String uriString = BASE_CONTENT_STRING_IMAGES + origId;
+                               UriTexture.invalidateCache(thumbId, 1024);
+                               try {
+                                       bitmap = UriTexture.createFromUri(context, uriString, 1024, 1024, thumbId, null);
+                               } catch (IOException e) {
+                                       return null;
+                               } catch (URISyntaxException e) {
+                                       return null;
+                               }
+                       } else {
+                               Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                               new Thread() {
+                                       public void run() {
+                                               try {
+                                                       Thread.sleep(5000);
+                                               } catch (InterruptedException e) {
+                                                       ;
+                                               }
+                                               try {
+                                                       MediaStore.Video.Thumbnails.cancelThumbnailRequest(context.getContentResolver(), origId);
+                                               } catch (Exception e) {
+                                                       ;
+                                               }
+                                       }
+                               }.start();
+                               bitmap = MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), origId,
+                                       MediaStore.Video.Thumbnails.MICRO_KIND, null);
+                       }
+                       if (bitmap == null) {
+                               return null;
+                       }
+                       final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight);
+                       return retVal;
+               } catch (InterruptedException e) {
+                       return null;
+               }
+       }
+
+       public static final byte[] writeBitmapToCache(final DiskCache thumbnailCache, final long thumbId, final long origId,
+               final Bitmap bitmap, final int thumbnailWidth, final int thumbnailHeight) {
+               final int width = bitmap.getWidth();
+               final int height = bitmap.getHeight();
+               // Detect faces to find the focal point, otherwise fall back to the
+               // image center.
+               int focusX = width / 2;
+               int focusY = height / 2;
+               // We have commented out face detection since it slows down the
+               // generation of the thumbnail and screennail.
+
+               // final FaceDetector faceDetector = new FaceDetector(width, height, 1);
+               // final FaceDetector.Face[] faces = new FaceDetector.Face[1];
+               // final int numFaces = faceDetector.findFaces(bitmap, faces);
+               // if (numFaces > 0 && faces[0].confidence() >=
+               // FaceDetector.Face.CONFIDENCE_THRESHOLD) {
+               // final PointF midPoint = new PointF();
+               // faces[0].getMidPoint(midPoint);
+               // focusX = (int) midPoint.x;
+               // focusY = (int) midPoint.y;
+               // }
+
+               // Crop to thumbnail aspect ratio biased towards the focus point.
+               int cropX;
+               int cropY;
+               int cropWidth;
+               int cropHeight;
+               float scaleFactor;
+               if (thumbnailWidth * height < thumbnailHeight * width) {
+                       // Vertically constrained.
+                       cropWidth = thumbnailWidth * height / thumbnailHeight;
+                       cropX = Math.max(0, Math.min(focusX - cropWidth / 2, width - cropWidth));
+                       cropY = 0;
+                       cropHeight = height;
+                       scaleFactor = (float) thumbnailHeight / height;
+               } else {
+                       // Horizontally constrained.
+                       cropHeight = thumbnailHeight * width / thumbnailWidth;
+                       cropY = Math.max(0, Math.min(focusY - cropHeight / 2, height - cropHeight));
+                       cropX = 0;
+                       cropWidth = width;
+                       scaleFactor = (float) thumbnailWidth / width;
+               }
+               final Bitmap finalBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.RGB_565);
+               final Canvas canvas = new Canvas(finalBitmap);
+               final Paint paint = new Paint();
+               paint.setFilterBitmap(true);
+               canvas.drawColor(0);
+               canvas.drawBitmap(bitmap, new Rect(cropX, cropY, cropX + cropWidth, cropY + cropHeight), new Rect(0, 0, thumbnailWidth,
+                       thumbnailHeight), paint);
+               bitmap.recycle();
+
+               // Store (long thumbnailId, short focusX, short focusY, JPEG data).
+               final ByteArrayOutputStream cacheOutput = new ByteArrayOutputStream(16384);
+               final DataOutputStream dataOutput = new DataOutputStream(cacheOutput);
+               byte[] retVal = null;
+               try {
+                       dataOutput.writeLong(origId);
+                       dataOutput.writeShort((int) ((focusX - cropX) * scaleFactor));
+                       dataOutput.writeShort((int) ((focusY - cropY) * scaleFactor));
+                       dataOutput.flush();
+                       finalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, cacheOutput);
+                       retVal = cacheOutput.toByteArray();
+                       synchronized (thumbnailCache) {
+                               thumbnailCache.put(thumbId, retVal);
+                       }
+                       cacheOutput.close();
+               } catch (Exception e) {
+                       ;
+               }
+               return retVal;
+       }
+
+       public CacheService() {
+               super("CacheService");
+       }
+
+       @Override
+       protected void onHandleIntent(final Intent intent) {
+               Log.i(TAG, "Starting CacheService");
+               if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) {
+                       sAlbumCache.deleteAll();
+                       putLocaleForAlbumCache(Locale.getDefault());
+               }
+               Locale locale = getLocaleForAlbumCache();
+               if (locale != null && locale.equals(Locale.getDefault())) {
+                       // The cache is in the same locale as the system locale.
+                       if (!isCacheReady(false)) {
+                               // The albums and their items have not yet been cached, we need
+                               // to run the service.
+                               startNewCacheThread();
+                       } else {
+                               startNewCacheThreadForDirtySets();
+                       }
+               } else {
+                       // The locale has changed, we need to regenerate the strings.
+                       sAlbumCache.deleteAll();
+                       putLocaleForAlbumCache(Locale.getDefault());
+                       startNewCacheThread();
+               }
+               if (intent.getBooleanExtra("checkthumbnails", false)) {
+                       startNewThumbnailThread(this);
+               } else {
+                       final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null);
+                       if (existingThread != null) {
+                               existingThread.interrupt();
+                       }
+               }
+       }
+
+       private static final void putLocaleForAlbumCache(final Locale locale) {
+               final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+               final DataOutputStream dos = new DataOutputStream(bos);
+               try {
+                       Utils.writeUTF(dos, locale.getCountry());
+                       Utils.writeUTF(dos, locale.getLanguage());
+                       Utils.writeUTF(dos, locale.getVariant());
+                       dos.flush();
+                       bos.flush();
+                       final byte[] data = bos.toByteArray();
+                       sAlbumCache.put(ALBUM_CACHE_LOCALE_INDEX, data);
+                       sAlbumCache.flush();
+                       dos.close();
+                       bos.close();
+               } catch (IOException e) {
+                       // Could not write locale to cache.
+                       Log.i(TAG, "Error writing locale to cache.");
+                       ;
+               }
+       }
+
+       private static final Locale getLocaleForAlbumCache() {
+               final byte[] data = sAlbumCache.get(ALBUM_CACHE_LOCALE_INDEX, 0);
+               if (data != null && data.length > 0) {
+                       ByteArrayInputStream bis = new ByteArrayInputStream(data);
+                       DataInputStream dis = new DataInputStream(bis);
+                       try {
+                               String country = Utils.readUTF(dis);
+                               if (country == null)
+                                       country = "";
+                               String language = Utils.readUTF(dis);
+                               if (language == null)
+                                       language = "";
+                               String variant = Utils.readUTF(dis);
+                               if (variant == null)
+                                       variant = "";
+                               final Locale locale = new Locale(language, country, variant);
+                               dis.close();
+                               bis.close();
+                               return locale;
+                       } catch (IOException e) {
+                               // Could not read locale in cache.
+                               Log.i(TAG, "Error reading locale from cache.");
+                               return null;
+                       }
+               }
+               return null;
+       }
+
+       private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) {
+               // Create a new thread.
+               final Thread newThread = new Thread() {
+                       public void run() {
+                               try {
+                                       action.run();
+                               } finally {
+                                       threadRef.compareAndSet(this, null);
+                               }
+                       }
+               };
+               newThread.setName(name);
+               newThread.start();
+
+               // Interrupt any existing thread.
+               final Thread existingThread = threadRef.getAndSet(newThread);
+               if (existingThread != null) {
+                       existingThread.interrupt();
+               }
+       }
+
+       public static final void startNewThumbnailThread(final Context context) {
+               restartThread(THUMBNAIL_THREAD, "ThumbnailRefresh", new Runnable() {
+                       public void run() {
+                               Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                               try {
+                                       // It is an optimization to prevent the thumbnailer from
+                                       // running while the application loads
+                                       Thread.sleep(THUMBNAILER_WAIT_IN_MS);
+                               } catch (InterruptedException e) {
+                                       return;
+                               }
+                               CacheService.buildThumbnails(context);
+                       }
+               });
+       }
+
+       private void startNewCacheThread() {
+               restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
+                       public void run() {
+                               refresh(CacheService.this);
+                       }
+               });
+       }
+
+       private void startNewCacheThreadForDirtySets() {
+               restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
+                       public void run() {
+                               refreshDirtySets(CacheService.this);
+                       }
+               });
+       }
+
+       private static final byte[] concat(final byte[] A, final byte[] B) {
+               final byte[] C = (byte[]) new byte[A.length + B.length];
+               System.arraycopy(A, 0, C, 0, A.length);
+               System.arraycopy(B, 0, C, A.length, B.length);
+               return C;
+       }
+
+       private static final long toLong(final byte[] data) {
+               // 8 bytes for a long
+               if (data == null || data.length < 8)
+                       return 0;
+               final ByteBuffer bBuffer = ByteBuffer.wrap(data);
+               final LongBuffer lBuffer = bBuffer.asLongBuffer();
+               final int numLongs = lBuffer.capacity();
+               return lBuffer.get(0);
+       }
+
+       private static final long[] toLongArray(final byte[] data) {
+               final ByteBuffer bBuffer = ByteBuffer.wrap(data);
+               final LongBuffer lBuffer = bBuffer.asLongBuffer();
+               final int numLongs = lBuffer.capacity();
+               final long[] retVal = new long[numLongs];
+               for (int i = 0; i < numLongs; ++i) {
+                       retVal[i] = lBuffer.get(i);
+               }
+               return retVal;
+       }
+
+       private static final byte[] longToByteArray(final long l) {
+               final byte[] bArray = new byte[8];
+               final ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
+               final LongBuffer lBuffer = bBuffer.asLongBuffer();
+               lBuffer.put(0, l);
+               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());
+
+               final ArrayList<MediaSet> sets = new ArrayList<MediaSet>();
+               LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>();
+               Log.i(TAG, "Building albums.");
+               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 ContentResolver cr = context.getContentResolver();
+
+               final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);
+               final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);
+               Cursor[] cursors = new Cursor[2];
+               cursors[0] = cursorImages;
+               cursors[1] = cursorVideos;
+               final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true);
+               try {
+                       if (sortCursor != null && sortCursor.moveToFirst()) {
+                               sets.ensureCapacity(sortCursor.getCount());
+                               acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount());
+                               MediaSet cameraSet = new MediaSet();
+                               cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;
+                               cameraSet.mName = context.getResources().getString(R.string.camera);
+                               sets.add(cameraSet);
+                               acceleratedSets.put(cameraSet.mId, cameraSet);
+                               do {
+                                       if (Thread.interrupted()) {
+                                               return;
+                                       }
+                                       long setId = sortCursor.getLong(BUCKET_ID_INDEX);
+                                       MediaSet mediaSet = findSet(setId, acceleratedSets);
+                                       if (mediaSet == null) {
+                                               mediaSet = new MediaSet();
+                                               mediaSet.mId = setId;
+                                               mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX);
+                                               sets.add(mediaSet);
+                                               acceleratedSets.put(setId, mediaSet);
+                                       }
+                                       mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0);
+                                       mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1);
+                               } while (sortCursor.moveToNext());
+                               sortCursor.close();
+                       }
+               } finally {
+                       if (sortCursor != null)
+                               sortCursor.close();
+               }
+
+               sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData);
+               writeSetsToCache(sets);
+               Log.i(TAG, "Done building albums.");
+               // 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);
+       }
+
+       private final static void refreshDirtySets(final Context context) {
+               final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
+               if (existingData != null && existingData.length > 0) {
+                       final long[] ids = toLongArray(existingData);
+                       final int numIds = ids.length;
+                       if (numIds > 0) {
+                               final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(numIds);
+                               final LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(numIds);
+                               for (int i = 0; i < numIds; ++i) {
+                                       final MediaSet set = new MediaSet();
+                                       set.mId = ids[i];
+                                       sets.add(set);
+                                       acceleratedSets.put(set.mId, set);
+                               }
+                               Log.i(TAG, "Refreshing dirty albums");
+                               populateMediaItemsForSets(context, sets, acceleratedSets, true);
+                       }
+               }
+               processQueuedDirty(context);
+               sAlbumCache.delete(ALBUM_CACHE_DIRTY_BUCKET_INDEX);
+       }
+
+       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 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);
+               Cursor[] cursors = new Cursor[2];
+               cursors[0] = cursorImages;
+               cursors[1] = cursorVideos;
+               final MergeCursor cursor = new MergeCursor(cursors);
+               long[] retVal = null;
+               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);
+                                               }
+                                       }
+                               } while (cursor.moveToNext());
+                       }
+               } finally {
+                       cursor.close();
+               }
+               processQueuedDirty(context);
+               return retVal;
+       }
+
+       private static final void processQueuedDirty(final Context context) {
+               if (QUEUE_DIRTY_SENSE) {
+                       QUEUE_DIRTY_SENSE = false;
+                       QUEUE_DIRTY_ALL = false;
+                       QUEUE_DIRTY_SET = false;
+                       computeDirtySets(context);
+               } else if (QUEUE_DIRTY_ALL) {
+                       QUEUE_DIRTY_ALL = false;
+                       QUEUE_DIRTY_SET = false;
+                       QUEUE_DIRTY_SENSE = false;
+                       refresh(context);
+               } else if (QUEUE_DIRTY_SET) {
+                       QUEUE_DIRTY_SET = false;
+                       // We don't mark QUEUE_DIRTY_SENSE because a set outside the dirty
+                       // sets might have gotten modified.
+                       refreshDirtySets(context);
+               }
+       }
+
+       private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets,
+               final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) {
+               if (sets == null || sets.size() == 0 || Thread.interrupted()) {
+                       return;
+               }
+               Log.i(TAG, "Building items.");
+               final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+               final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+               final ContentResolver cr = context.getContentResolver();
+
+               String whereClause = null;
+               if (useWhere) {
+                       int numSets = sets.size();
+                       StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in (");
+                       for (int i = 0; i < numSets; ++i) {
+                               whereString.append(sets.get(i).mId);
+                               if (i != numSets - 1) {
+                                       whereString.append(",");
+                               }
+                       }
+                       whereString.append(")");
+                       whereClause = whereString.toString();
+                       Log.i(TAG, "Updating dirty albums where " + whereClause);
+               }
+
+               final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER);
+               final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER);
+               final Cursor[] cursors = new Cursor[2];
+               cursors[0] = cursorImages;
+               cursors[1] = cursorVideos;
+               final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true);
+               if (Thread.interrupted()) {
+                       return;
+               }
+               try {
+                       if (sortCursor != null && sortCursor.moveToFirst()) {
+                               final int count = sortCursor.getCount();
+                               final int numSets = sets.size();
+                               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 {
+                                       if (Thread.interrupted()) {
+                                               return;
+                                       }
+                                       final MediaItem item = new MediaItem();
+                                       final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1);
+                                       if (isVideo) {
+                                               populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);
+                                       } else {
+                                               populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);
+                                       }
+                                       final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX);
+                                       final MediaSet set = findSet(setId, acceleratedSets);
+                                       if (set != null) {
+                                               set.getItems().add(item);
+                                       }
+                               } while (sortCursor.moveToNext());
+                       }
+               } finally {
+                       if (sortCursor != null)
+                               sortCursor.close();
+               }
+               if (sets.size() > 0) {
+                       writeItemsToCache(sets);
+                       Log.i(TAG, "Done building items.");
+               }
+       }
+
+       private static final void writeSetsToCache(final ArrayList<MediaSet> sets) {
+               final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+               final int numSets = sets.size();
+               final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
+               try {
+                       dos.writeInt(numSets);
+                       for (int i = 0; i < numSets; ++i) {
+                               if (Thread.interrupted()) {
+                                       return;
+                               }
+                               final MediaSet set = sets.get(i);
+                               dos.writeLong(set.mId);
+                               Utils.writeUTF(dos, set.mName);
+                               dos.writeBoolean(set.mHasImages);
+                               dos.writeBoolean(set.mHasVideos);
+                       }
+                       dos.flush();
+                       sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray());
+                       dos.close();
+                       if (numSets == 0) {
+                               sAlbumCache.deleteAll();
+                               putLocaleForAlbumCache(Locale.getDefault());
+                       }
+                       sAlbumCache.flush();
+               } catch (IOException e) {
+                       Log.e(TAG, "Error writing albums to diskcache.");
+                       sAlbumCache.deleteAll();
+                       putLocaleForAlbumCache(Locale.getDefault());
+               }
+       }
+
+       private static final void writeItemsToCache(final ArrayList<MediaSet> sets) {
+               final int numSets = sets.size();
+               for (int i = 0; i < numSets; ++i) {
+                       if (Thread.interrupted()) {
+                               return;
+                       }
+                       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));
+               try {
+                       final ArrayList<MediaItem> items = set.getItems();
+                       final int numItems = items.size();
+                       dos.writeInt(numItems);
+                       dos.writeLong(set.mMinTimestamp);
+                       dos.writeLong(set.mMaxTimestamp);
+                       for (int i = 0; i < numItems; ++i) {
+                               MediaItem item = items.get(i);
+                               if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) {
+                                       // Reverse the display order for the camera bucket - want
+                                       // the latest first.
+                                       item = items.get(numItems - i - 1);
+                               }
+                               dos.writeLong(item.mId);
+                               Utils.writeUTF(dos, item.mCaption);
+                               Utils.writeUTF(dos, item.mMimeType);
+                               dos.writeInt(item.getMediaType());
+                               dos.writeDouble(item.mLatitude);
+                               dos.writeDouble(item.mLongitude);
+                               dos.writeLong(item.mDateTakenInMs);
+                               dos.writeBoolean(item.mTriedRetrievingExifDateTaken);
+                               dos.writeLong(item.mDateAddedInSec);
+                               dos.writeLong(item.mDateModifiedInSec);
+                               dos.writeInt(item.mDurationInSec);
+                               dos.writeInt((int) item.mRotation);
+                               Utils.writeUTF(dos, item.mFilePath);
+                       }
+                       dos.flush();
+                       sAlbumCache.put(set.mId, bos.toByteArray());
+                       dos.close();
+               } catch (IOException e) {
+                       Log.e(TAG, "Error writing to diskcache for set " + set.mName);
+                       sAlbumCache.deleteAll();
+                       putLocaleForAlbumCache(Locale.getDefault());
+               }
+       }
+
+       private static final MediaSet findSet(final long id, final LongSparseArray<MediaSet> acceleratedTable) {
+               // This is the accelerated lookup table for the MediaSet based on set
+               // id.
+               return acceleratedTable.get(id);
+       }
 }
index 234e916..fef6bba 100644 (file)
@@ -21,7 +21,7 @@ public class BackgroundLayer extends Layer {
     private static final int ADAPTIVE_BACKGROUND_WIDTH = 256;
     private static final int ADAPTIVE_BACKGROUND_HEIGHT = 128;
 
-    BackgroundLayer(GridLayer layer) {
+    public BackgroundLayer(GridLayer layer) {
         mGridLayer = layer;
     }
 
index f09b151..3a719b3 100644 (file)
@@ -2,6 +2,7 @@ package com.cooliris.media;
 
 import java.util.ArrayList;
 
+
 import android.util.Log;
 
 public final class ConcatenatedDataSource implements DataSource {
index c05eacf..90b6df2 100644 (file)
@@ -2,6 +2,7 @@ package com.cooliris.media;
 
 import java.util.ArrayList;
 
+
 public interface DataSource {
     // Load the sets to be displayed.
     void loadMediaSets(final MediaFeed feed);
diff --git a/src/com/cooliris/media/FlatLocalDataSource.java b/src/com/cooliris/media/FlatLocalDataSource.java
deleted file mode 100644 (file)
index 87caca5..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-//package com.cooliris.media;
-//
-//// Deprecated class. Need to remove from perforce
-//
-//import java.util.ArrayList;
-//
-//public class FlatLocalDataSource implements MediaFeed.DataSource {
-//    private final boolean mIncludeImages;
-//    private final boolean mIncludeVideos;
-//    
-//    public FlatLocalDataSource(boolean includeImages, boolean includeVideos) {
-//        mIncludeImages = includeImages;
-//        mIncludeVideos = includeVideos;
-//    }
-//    
-//    public DiskCache getThumbnailCache() {
-//        return LocalDataSource.sThumbnailCache;
-//    }
-//
-//    public void loadItemsForSet(MediaFeed feed, MediaSet parentSet, int rangeStart, int rangeEnd) {
-//        // TODO Auto-generated method stub
-//        
-//    }
-//
-//    public void loadMediaSets(MediaFeed feed) {
-//        MediaSet set = feed.addMediaSet(0, this);
-//        set.name = "Local Media";
-//    }
-//
-//    public boolean performOperation(int operation, ArrayList<MediaBucket> mediaBuckets, Object data) {
-//        // TODO Auto-generated method stub
-//        return false;
-//    }
-//
-//}
index 2a2a170..46fc3dd 100644 (file)
@@ -2,6 +2,7 @@ package com.cooliris.media;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.HashMap;
 import java.util.TimeZone;
 
 import android.app.Activity;
@@ -27,349 +28,365 @@ import com.cooliris.wallpaper.RandomDataSource;
 import com.cooliris.wallpaper.Slideshow;
 
 public final class Gallery extends Activity {
-    public static final TimeZone CURRENT_TIME_ZONE = TimeZone.getDefault();
-    public static float PIXEL_DENSITY = 0.0f;
-    public static boolean NEEDS_REFRESH = true;
+       public static final TimeZone CURRENT_TIME_ZONE = TimeZone.getDefault();
+       public static float PIXEL_DENSITY = 0.0f;
+       public static final int CROP_MSG_INTERNAL = 100;
 
-    private static final String TAG = "Gallery";
-    public static final int CROP_MSG_INTERNAL = 100;
-    private static final int CROP_MSG = 10;
-    private RenderView mRenderView = null;
-    private GridLayer mGridLayer;
-    private final Handler mHandler = new Handler();
-    private ReverseGeocoder mReverseGeocoder;
-    private boolean mPause;
-    private MediaScannerConnection mConnection;
-    private WakeLock mWakeLock;
-    private static final boolean TEST_WALLPAPER = false;
+       private static final String TAG = "Gallery";
+       private static final int CROP_MSG = 10;
+       private RenderView mRenderView = null;
+       private GridLayer mGridLayer;
+       private final Handler mHandler = new Handler();
+       private ReverseGeocoder mReverseGeocoder;
+       private boolean mPause;
+       private MediaScannerConnection mConnection;
+       private WakeLock mWakeLock;
+       private HashMap<String, Boolean> mAccountsEnabled;
+       private static final boolean TEST_WALLPAPER = false;
 
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final boolean imageManagerHasStorage = ImageManager.quickHasStorage();
-        if (TEST_WALLPAPER || (isViewIntent() && getIntent().getData().equals(Images.Media.EXTERNAL_CONTENT_URI))) {
-            if (!imageManagerHasStorage) {
-                Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
-                finish();
-            } else {
-                PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-                mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
-                mWakeLock.acquire();
-                Slideshow slideshow = new Slideshow(this);
-                slideshow.setDataSource(new RandomDataSource());
-                setContentView(slideshow);
-            }
-            return;
-        }
-        boolean isCacheReady = CacheService.isCacheReady(false);
-        CacheService.startCache(this, false);
-        if (PIXEL_DENSITY == 0.0f) {
-            DisplayMetrics metrics = new DisplayMetrics();
-            getWindowManager().getDefaultDisplay().getMetrics(metrics);
-            PIXEL_DENSITY = metrics.density;
-        }
-        mReverseGeocoder = new ReverseGeocoder(this);
-        mRenderView = new RenderView(this);
-        mGridLayer = new GridLayer(this, (int) (96.0f * PIXEL_DENSITY), (int) (72.0f * PIXEL_DENSITY), new GridLayoutInterface(4),
-                mRenderView);
-        mRenderView.setRootLayer(mGridLayer);
-        setContentView(mRenderView);
-        if (!isPickIntent() && !isViewIntent()) {
-            PicasaDataSource picasaDataSource = new PicasaDataSource(this);
-            if (imageManagerHasStorage) {
-                LocalDataSource localDataSource = new LocalDataSource(this);
-                ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
-                mGridLayer.setDataSource(combinedDataSource);
-            } else {
-                mGridLayer.setDataSource(picasaDataSource);
-            }
-            if (!imageManagerHasStorage) {
-                Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
-            } else {
-                if (!isCacheReady) {
-                    Toast.makeText(this, getResources().getString(R.string.loading_new), Toast.LENGTH_LONG).show();
-                } else {
-                    // Toast.makeText(this, getResources().getString(R.string.initializing), Toast.LENGTH_SHORT).show();
-                }
-            }
-        } else if (!isViewIntent()) {
-            Intent intent = getIntent();
-            if (intent != null) {
-                String type = intent.resolveType(this);
-                boolean includeImages = isImageType(type);
-                boolean includeVideos = isVideoType(type);
-                LocalDataSource localDataSource = new LocalDataSource(this);
-                ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
-                if (includeImages) {
-                    PicasaDataSource picasaDataSource = new PicasaDataSource(this);
-                    if (imageManagerHasStorage) {
-                        ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
-                        mGridLayer.setDataSource(combinedDataSource);
-                    } else {
-                        mGridLayer.setDataSource(picasaDataSource);
-                    }
-                } else {
-                    mGridLayer.setDataSource(localDataSource);
-                }
-                mGridLayer.setPickIntent(true);
-                if (!imageManagerHasStorage) {
-                    Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
-                } else {
-                    Toast.makeText(this, getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG).show();
-                }
-            }
-        } else {
-            // View intent for images.
-            Uri uri = getIntent().getData();
-            boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
-            SingleDataSource localDataSource = new SingleDataSource(this, uri.toString(), slideshow);
-            PicasaDataSource picasaDataSource = new PicasaDataSource(this);
-            ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
-            mGridLayer.setDataSource(combinedDataSource);
-            mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
-            if (SingleDataSource.isSingleImageMode(uri.toString())) {
-                mGridLayer.setSingleImage(false);
-            } else if (slideshow) {
-                mGridLayer.setSingleImage(true);
-                mGridLayer.startSlideshow();
-            }
-            // Toast.makeText(this, getResources().getString(R.string.initializing), Toast.LENGTH_SHORT).show();
-        }
-        Log.i(TAG, "onCreate");
-    }
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+               final boolean imageManagerHasStorage = ImageManager.quickHasStorage();
+               if (TEST_WALLPAPER || (isViewIntent() && getIntent().getData().equals(Images.Media.EXTERNAL_CONTENT_URI))) {
+                       if (!imageManagerHasStorage) {
+                               Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
+                               finish();
+                       } else {
+                               PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+                               mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
+                               mWakeLock.acquire();
+                               Slideshow slideshow = new Slideshow(this);
+                               slideshow.setDataSource(new RandomDataSource());
+                               setContentView(slideshow);
+                       }
+                       return;
+               }
+               final boolean isCacheReady = CacheService.isCacheReady(false);
+               CacheService.startCache(this, false);
+               if (PIXEL_DENSITY == 0.0f) {
+                       DisplayMetrics metrics = new DisplayMetrics();
+                       getWindowManager().getDefaultDisplay().getMetrics(metrics);
+                       PIXEL_DENSITY = metrics.density;
+               }
+               mReverseGeocoder = new ReverseGeocoder(this);
+               mRenderView = new RenderView(this);
+               mGridLayer = new GridLayer(this, (int) (96.0f * PIXEL_DENSITY), (int) (72.0f * PIXEL_DENSITY), new GridLayoutInterface(4),
+                       mRenderView);
+               mRenderView.setRootLayer(mGridLayer);
+               setContentView(mRenderView);
+               
+               // Creating the DataSource objects
+               final PicasaDataSource picasaDataSource = new PicasaDataSource(this);
+               final LocalDataSource localDataSource = new LocalDataSource(this);
+               final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
+               
+               // Depending upon the intent, we assign the right dataSource.
+               if (!isPickIntent() && !isViewIntent()) {
+                       if (imageManagerHasStorage) {
+                               mGridLayer.setDataSource(combinedDataSource);
+                       } else {
+                               mGridLayer.setDataSource(picasaDataSource);
+                       }
+                       if (!imageManagerHasStorage) {
+                               Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
+                       } else if (!isCacheReady) {
+                               Toast.makeText(this, getResources().getString(R.string.loading_new), Toast.LENGTH_LONG).show();
+                       }
+               } else if (!isViewIntent()) {
+                       final Intent intent = getIntent();
+                       if (intent != null) {
+                               final String type = intent.resolveType(this);
+                               boolean includeImages = isImageType(type);
+                               boolean includeVideos = isVideoType(type);
+                               ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
+                               if (includeImages) {
+                                       if (imageManagerHasStorage) {
+                                               mGridLayer.setDataSource(combinedDataSource);
+                                       } else {
+                                               mGridLayer.setDataSource(picasaDataSource);
+                                       }
+                               } else {
+                                       mGridLayer.setDataSource(localDataSource);
+                               }
+                               mGridLayer.setPickIntent(true);
+                               if (!imageManagerHasStorage) {
+                                       Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
+                               } else {
+                                       Toast.makeText(this, getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG).show();
+                               }
+                       }
+               } else {
+                       // View intent for images.
+                       Uri uri = getIntent().getData();
+                       boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
+                       final SingleDataSource singleDataSource = new SingleDataSource(this, uri.toString(), slideshow);
+                       final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
+                       mGridLayer.setDataSource(singleCombinedDataSource);
+                       mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
+                       if (SingleDataSource.isSingleImageMode(uri.toString())) {
+                               mGridLayer.setSingleImage(false);
+                       } else if (slideshow) {
+                               mGridLayer.setSingleImage(true);
+                               mGridLayer.startSlideshow();
+                       }
+               }
+               // We record the set of enabled accounts for picasa.
+               mAccountsEnabled = PicasaDataSource.getAccountStatus(this);
+               Log.i(TAG, "onCreate");
+       }
 
-    public ReverseGeocoder getReverseGeocoder() {
-        return mReverseGeocoder;
-    }
+       public ReverseGeocoder getReverseGeocoder() {
+               return mReverseGeocoder;
+       }
 
-    public Handler getHandler() {
-        return mHandler;
-    }
+       public Handler getHandler() {
+               return mHandler;
+       }
 
-    @Override
-    public void onRestart() {
-        super.onRestart();
-    }
+       @Override
+       public void onRestart() {
+               super.onRestart();
+       }
 
-    @Override
-    public void onStart() {
-        super.onStart();
-    }
+       @Override
+       public void onStart() {
+               super.onStart();
+       }
 
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mRenderView != null)
-            mRenderView.onResume();
-        if (NEEDS_REFRESH) {
-            NEEDS_REFRESH = false;
-            CacheService.markDirtyImmediate(LocalDataSource.CAMERA_BUCKET_ID);
-            CacheService.markDirtyImmediate(LocalDataSource.DOWNLOAD_BUCKET_ID);
-            CacheService.startCache(this, false);
-        }
-        mPause = false;
-    }
+       @Override
+       public void onResume() {
+               super.onResume();
+               if (mRenderView != null)
+                       mRenderView.onResume();
+               if (mPause) {
+                       // We check to see if the authenticated accounts have changed, and
+                       // if so, reload the datasource.
+                       HashMap<String, Boolean> accountsEnabled = PicasaDataSource.getAccountStatus(this);
+                       String[] keys = new String[accountsEnabled.size()];
+                       keys = accountsEnabled.keySet().toArray(keys);
+                       int numKeys = keys.length;
+                       for (int i = 0; i < numKeys; ++i) {
+                               String key = keys[i];
+                               boolean newValue = accountsEnabled.get(key).booleanValue();
+                               boolean oldValue = false;
+                               Boolean oldValObj = mAccountsEnabled.get(key);
+                               if (oldValObj != null) {
+                                       oldValue = oldValObj.booleanValue();
+                               }
+                               if (oldValue != newValue) {
+                                       // Reload the datasource.
+                                       mGridLayer.setDataSource(mGridLayer.getDataSource());
+                                       break;
+                               }
+                       }
+                       mAccountsEnabled = accountsEnabled;
+                       mPause = false;
+               }
+       }
 
-    @Override
-    public void onPause() {
-        super.onPause();
-        if (mRenderView != null)
-            mRenderView.onPause();
-        mPause = true;
-    }
+       @Override
+       public void onPause() {
+               super.onPause();
+               if (mRenderView != null)
+                       mRenderView.onPause();
+               mPause = true;
+       }
 
-    public boolean isPaused() {
-        return mPause;
-    }
+       public boolean isPaused() {
+               return mPause;
+       }
 
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mGridLayer != null)
-            mGridLayer.stop();
-        if (mReverseGeocoder != null) {
-            mReverseGeocoder.flushCache();
-        }
-        LocalDataSource.sThumbnailCache.flush();
-        LocalDataSource.sThumbnailCacheVideo.flush();
-        PicasaDataSource.sThumbnailCache.flush();
-        CacheService.startCache(this, true);
-    }
+       @Override
+       public void onStop() {
+               super.onStop();
+               if (mGridLayer != null)
+                       mGridLayer.stop();
+               if (mReverseGeocoder != null) {
+                       mReverseGeocoder.flushCache();
+               }
+               LocalDataSource.sThumbnailCache.flush();
+               LocalDataSource.sThumbnailCacheVideo.flush();
+               PicasaDataSource.sThumbnailCache.flush();
+               CacheService.startCache(this, true);
+       }
 
-    @Override
-    public void onDestroy() {
-        // Force GLThread to exit.
-        setContentView(R.layout.main);
-        if (mGridLayer != null) {
-            DataSource dataSource = mGridLayer.getDataSource();
-            if (dataSource != null) {
-                dataSource.shutdown();
-            }
-            mGridLayer.shutdown();
-        }
-        if (mWakeLock != null) {
-            if (mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
-            mWakeLock = null;
-        }
-        if (mReverseGeocoder != null)
-            mReverseGeocoder.shutdown();
-        if (mRenderView != null) {
-            mRenderView.shutdown();
-            mRenderView = null;
-        }
-        mGridLayer = null;
-        super.onDestroy();
-        Log.i(TAG, "onDestroy");
-    }
+       @Override
+       public void onDestroy() {
+               // Force GLThread to exit.
+               setContentView(R.layout.main);
+               if (mGridLayer != null) {
+                       DataSource dataSource = mGridLayer.getDataSource();
+                       if (dataSource != null) {
+                               dataSource.shutdown();
+                       }
+                       mGridLayer.shutdown();
+               }
+               if (mWakeLock != null) {
+                       if (mWakeLock.isHeld()) {
+                               mWakeLock.release();
+                       }
+                       mWakeLock = null;
+               }
+               if (mReverseGeocoder != null)
+                       mReverseGeocoder.shutdown();
+               if (mRenderView != null) {
+                       mRenderView.shutdown();
+                       mRenderView = null;
+               }
+               mGridLayer = null;
+               super.onDestroy();
+               Log.i(TAG, "onDestroy");
+       }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        if (mGridLayer != null) {
-            mGridLayer.markDirty(30);
-        }
-        if (mRenderView != null)
-            mRenderView.requestRender();
-        Log.i(TAG, "onConfigurationChanged");
-    }
+       @Override
+       public void onConfigurationChanged(Configuration newConfig) {
+               super.onConfigurationChanged(newConfig);
+               if (mGridLayer != null) {
+                       mGridLayer.markDirty(30);
+               }
+               if (mRenderView != null)
+                       mRenderView.requestRender();
+               Log.i(TAG, "onConfigurationChanged");
+       }
 
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mRenderView != null) {
-            return mRenderView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
-        } else {
-            return super.onKeyDown(keyCode, event);
-        }
-    }
+       @Override
+       public boolean onKeyDown(int keyCode, KeyEvent event) {
+               if (mRenderView != null) {
+                       return mRenderView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+               } else {
+                       return super.onKeyDown(keyCode, event);
+               }
+       }
 
-    private boolean isPickIntent() {
-        String action = getIntent().getAction();
-        return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
-    }
+       private boolean isPickIntent() {
+               String action = getIntent().getAction();
+               return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
+       }
 
-    private boolean isViewIntent() {
-        String action = getIntent().getAction();
-        return Intent.ACTION_VIEW.equals(action);
-    }
+       private boolean isViewIntent() {
+               String action = getIntent().getAction();
+               return Intent.ACTION_VIEW.equals(action);
+       }
 
-    private boolean isImageType(String type) {
-        return type.equals("vnd.android.cursor.dir/image") || type.equals("image/*");
-    }
+       private boolean isImageType(String type) {
+               return type.equals("vnd.android.cursor.dir/image") || type.equals("image/*");
+       }
 
-    private boolean isVideoType(String type) {
-        return type.equals("vnd.android.cursor.dir/video") || type.equals("video/*");
-    }
+       private boolean isVideoType(String type) {
+               return type.equals("vnd.android.cursor.dir/video") || type.equals("video/*");
+       }
 
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-        case CROP_MSG: {
-            if (resultCode == RESULT_OK) {
-                setResult(resultCode, data);
-                finish();
-            }
-            break;
-        }
-        case CROP_MSG_INTERNAL: {
-            // We cropped an image, we must try to set the focus of the camera to that image.
-            if (resultCode == RESULT_OK) {
-                String contentUri = data.getAction();
-                if (mGridLayer != null) {
-                    mGridLayer.focusItem(contentUri);
-                }
-            }
-            break;
-        }
-        }
-    }
+       @Override
+       protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+               switch (requestCode) {
+               case CROP_MSG: {
+                       if (resultCode == RESULT_OK) {
+                               setResult(resultCode, data);
+                               finish();
+                       }
+                       break;
+               }
+               case CROP_MSG_INTERNAL: {
+                       // We cropped an image, we must try to set the focus of the camera
+                       // to that image.
+                       if (resultCode == RESULT_OK) {
+                               String contentUri = data.getAction();
+                               if (mGridLayer != null) {
+                                       mGridLayer.focusItem(contentUri);
+                               }
+                       }
+                       break;
+               }
+               }
+       }
 
-    @Override
-    public void onLowMemory() {
-        if (mRenderView != null) {
-            mRenderView.handleLowMemory();
-        }
-    }
+       @Override
+       public void onLowMemory() {
+               if (mRenderView != null) {
+                       mRenderView.handleLowMemory();
+               }
+       }
 
-    public void launchCropperOrFinish(final MediaItem item) {
-        final Bundle myExtras = getIntent().getExtras();
-        String cropValue = myExtras != null ? myExtras.getString("crop") : null;
-        final String contentUri = item.mContentUri;
-        if (cropValue != null) {
-            Bundle newExtras = new Bundle();
-            if (cropValue.equals("circle")) {
-                newExtras.putString("circleCrop", "true");
-            }
-            Intent cropIntent = new Intent();
-            cropIntent.setData(Uri.parse(contentUri));
-            cropIntent.setClass(this, CropImage.class);
-            cropIntent.putExtras(newExtras);
-            // Pass through any extras that were passed in.
-            cropIntent.putExtras(myExtras);
-            startActivityForResult(cropIntent, CROP_MSG);
-        } else {
-            if (contentUri.startsWith("http://")) {
-                // This is a http uri, we must save it locally first and generate a content uri from it.
-                final ProgressDialog dialog = ProgressDialog.show(this, this.getResources().getString(R.string.initializing),
-                        getResources().getString(R.string.running_face_detection), true, false);
-                if (contentUri != null) {
-                    MediaScannerConnection.MediaScannerConnectionClient client = new MediaScannerConnection.MediaScannerConnectionClient() {
-                        public void onMediaScannerConnected() {
-                            if (mConnection != null) {
-                                try {
-                                    final String path = UriTexture.writeHttpDataInDirectory(Gallery.this, contentUri,
-                                            LocalDataSource.DOWNLOAD_BUCKET_NAME);
-                                    if (path != null) {
-                                        mConnection.scanFile(path, item.mMimeType);
-                                    } else {
-                                        shutdown("");
-                                    }
-                                } catch (Exception e) {
-                                    shutdown("");
-                                }
-                            }
-                        }
+       public void launchCropperOrFinish(final MediaItem item) {
+               final Bundle myExtras = getIntent().getExtras();
+               String cropValue = myExtras != null ? myExtras.getString("crop") : null;
+               final String contentUri = item.mContentUri;
+               if (cropValue != null) {
+                       Bundle newExtras = new Bundle();
+                       if (cropValue.equals("circle")) {
+                               newExtras.putString("circleCrop", "true");
+                       }
+                       Intent cropIntent = new Intent();
+                       cropIntent.setData(Uri.parse(contentUri));
+                       cropIntent.setClass(this, CropImage.class);
+                       cropIntent.putExtras(newExtras);
+                       // Pass through any extras that were passed in.
+                       cropIntent.putExtras(myExtras);
+                       startActivityForResult(cropIntent, CROP_MSG);
+               } else {
+                       if (contentUri.startsWith("http://")) {
+                               // This is a http uri, we must save it locally first and
+                               // generate a content uri from it.
+                               final ProgressDialog dialog = ProgressDialog.show(this, this.getResources().getString(R.string.initializing),
+                                       getResources().getString(R.string.running_face_detection), true, false);
+                               if (contentUri != null) {
+                                       MediaScannerConnection.MediaScannerConnectionClient client = new MediaScannerConnection.MediaScannerConnectionClient() {
+                                               public void onMediaScannerConnected() {
+                                                       if (mConnection != null) {
+                                                               try {
+                                                                       final String path = UriTexture.writeHttpDataInDirectory(Gallery.this, contentUri,
+                                                                               LocalDataSource.DOWNLOAD_BUCKET_NAME);
+                                                                       if (path != null) {
+                                                                               mConnection.scanFile(path, item.mMimeType);
+                                                                       } else {
+                                                                               shutdown("");
+                                                                       }
+                                                               } catch (Exception e) {
+                                                                       shutdown("");
+                                                               }
+                                                       }
+                                               }
 
-                        public void onScanCompleted(String path, Uri uri) {
-                            shutdown(uri.toString());
-                        }
+                                               public void onScanCompleted(String path, Uri uri) {
+                                                       shutdown(uri.toString());
+                                               }
 
-                        public void shutdown(String uri) {
-                            dialog.dismiss();
-                            performReturn(myExtras, uri.toString());
-                            if (mConnection != null) {
-                                mConnection.disconnect();
-                            }
-                        }
-                    };
-                    MediaScannerConnection connection = new MediaScannerConnection(Gallery.this, client);
-                    connection.connect();
-                    mConnection = connection;
-                }
-            } else {
-                performReturn(myExtras, contentUri);
-            }
-        }
-    }
+                                               public void shutdown(String uri) {
+                                                       dialog.dismiss();
+                                                       performReturn(myExtras, uri.toString());
+                                                       if (mConnection != null) {
+                                                               mConnection.disconnect();
+                                                       }
+                                               }
+                                       };
+                                       MediaScannerConnection connection = new MediaScannerConnection(Gallery.this, client);
+                                       connection.connect();
+                                       mConnection = connection;
+                               }
+                       } else {
+                               performReturn(myExtras, contentUri);
+                       }
+               }
+       }
 
-    private void performReturn(Bundle myExtras, String contentUri) {
-        Intent result = new Intent(null, Uri.parse(contentUri));
-        if (myExtras != null && myExtras.getBoolean("return-data")) {
-            // The size of a transaction should be below 100K.
-            Bitmap bitmap = null;
-            try {
-                bitmap = UriTexture.createFromUri(this, contentUri, 1024, 1024, 0, null);
-            } catch (IOException e) {
-                ;
-            } catch (URISyntaxException e) {
-                ;
-            }
-            if (bitmap != null) {
-                result.putExtra("data", bitmap);
-            }
-        }
-        setResult(RESULT_OK, result);
-        finish();
-    }
+       private void performReturn(Bundle myExtras, String contentUri) {
+               Intent result = new Intent(null, Uri.parse(contentUri));
+               if (myExtras != null && myExtras.getBoolean("return-data")) {
+                       // The size of a transaction should be below 100K.
+                       Bitmap bitmap = null;
+                       try {
+                               bitmap = UriTexture.createFromUri(this, contentUri, 1024, 1024, 0, null);
+                       } catch (IOException e) {
+                               ;
+                       } catch (URISyntaxException e) {
+                               ;
+                       }
+                       if (bitmap != null) {
+                               result.putExtra("data", bitmap);
+                       }
+               }
+               setResult(RESULT_OK, result);
+               finish();
+       }
 }
index 0e43ef3..b4eb1c2 100644 (file)
@@ -1,5 +1,6 @@
 package com.cooliris.media;
 
+
 public final class GridCameraManager {
     private final GridCamera mCamera;
     private static final Pool<Vector3f> sPool;
index c7050cb..94a15d8 100644 (file)
@@ -182,6 +182,9 @@ public final class GridInputProcessor implements GestureDetector.OnGestureListen
                     layer.setZoomValue(1.0f);
             }
             if (keyCode == KeyEvent.KEYCODE_MENU) {
+               if (mLayer.getFeed() != null && mLayer.getFeed().isSingleImageMode()) {
+                       return true;
+               }
                 if (layer.getHud().getMode() == HudLayer.MODE_NORMAL)
                     layer.enterSelectionMode();
                 else
index dc2b09f..8cd05c2 100644 (file)
@@ -7,1426 +7,1437 @@ import android.hardware.SensorEvent;
 import android.opengl.GLU;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.content.Context;
 
 public final class GridLayer extends RootLayer implements MediaFeed.Listener, TimeBar.Listener {
-    public static final int STATE_MEDIA_SETS = 0;
-    public static final int STATE_GRID_VIEW = 1;
-    public static final int STATE_FULL_SCREEN = 2;
-    public static final int STATE_TIMELINE = 3;
-
-    public static final int ANCHOR_LEFT = 0;
-    public static final int ANCHOR_RIGHT = 1;
-    public static final int ANCHOR_CENTER = 2;
-
-    public static final int MAX_ITEMS_PER_SLOT = 12;
-    public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
-    public static final int MAX_DISPLAY_SLOTS = 96;
-    public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;
-
-    private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
-
-    private static HudLayer sHud;
-    private int mState;
-    private static final IndexRange sBufferedVisibleRange = new IndexRange();
-    private static final IndexRange sVisibleRange = new IndexRange();
-    private static final IndexRange sPreviousDataRange = new IndexRange();
-    private static final IndexRange sCompleteRange = new IndexRange();
-    
-    private static final Pool<Vector3f> sTempVec;
-    private static final Pool<Vector3f> sTempVecAlt;
-    static {
-        Vector3f[] vectorPool = new Vector3f[128];
-        int length = vectorPool.length;
-        for (int i = 0; i < length; ++i) {
-            vectorPool[i] = new Vector3f();
-        }
-        Vector3f[] vectorPoolRenderThread = new Vector3f[128];
-        length = vectorPoolRenderThread.length;
-        for (int i = 0; i < length; ++i) {
-            vectorPoolRenderThread[i] = new Vector3f();
-        }
-        sTempVec = new Pool<Vector3f>(vectorPool);
-        sTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
-    }
-    
-    private static final ArrayList<MediaItem> sTempList = new ArrayList<MediaItem>();
-    private static final MediaItem[] sTempHash = new MediaItem[64];
-    
-    private static final Vector3f sDeltaAnchorPositionUncommited = new Vector3f();
-    private static Vector3f sDeltaAnchorPosition = new Vector3f();
-
-    // The display primitives.
-    private GridDrawables mDrawables;
-    private float mSelectedAlpha = 0.0f;
-    private float mTargetAlpha = 0.0f;
-
-    private GridCamera mCamera;
-    private GridCameraManager mCameraManager;
-    private GridDrawManager mDrawManager;
-    private GridInputProcessor mInputProcessor;
-
-    private boolean mFeedAboutToChange;
-    private boolean mPerformingLayoutChange;
-    private boolean mFeedChanged;
-
-    private final LayoutInterface mLayoutInterface;
-    private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);
-    
-    private MediaFeed mMediaFeed;
-    private boolean mInAlbum = false;
-    private int mCurrentExpandedSlot;
-
-    private static final DisplayList sDisplayList = new DisplayList();
-    private static final DisplayItem[] sDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
-    private static final DisplaySlot[] sDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
-    private static ArrayList<MediaItem> sVisibleItems;
-    
-    private float mTimeElapsedSinceTransition;
-    private BackgroundLayer mBackground;
-    private boolean mLocationFilter;
-    private float mZoomValue = 1.0f;
-    private float mCurrentFocusItemWidth = 1.0f;
-    private float mCurrentFocusItemHeight = 1.0f;
-    private float mTimeElapsedSinceGridViewReady = 0.0f;
-
-    private boolean mSlideshowMode;
-    private boolean mNoDeleteMode = false;
-    private float mTimeElapsedSinceView;
-    private static final MediaBucketList sBucketList = new MediaBucketList();
-    private float mTimeElapsedSinceStackViewReady;
-
-    private Context mContext;
-    private RenderView mView;
-    private boolean mPickIntent;
-    private boolean mViewIntent;
-    private WakeLock mWakeLock;
-    private int mStartMemoryRange;
-    private int mFramesDirty;
-    private String mRequestFocusContentUri;
-    private int mFrameCount;
-
-    public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
-        mBackground = new BackgroundLayer(this);
-        mContext = context;
-        mView = view;
-        
-        DisplaySlot[] displaySlots = sDisplaySlots;
-        for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
-            DisplaySlot slot = new DisplaySlot();
-            displaySlots[i] = slot;
-        }
-        mLayoutInterface = layoutInterface;
-        mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
-        mDrawables = new GridDrawables(itemWidth, itemHeight);
-        sBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
-        sVisibleRange.set(Shared.INVALID, Shared.INVALID);
-        sCompleteRange.set(Shared.INVALID, Shared.INVALID);
-        sPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
-        sDeltaAnchorPosition.set(0, 0, 0);
-        sDeltaAnchorPositionUncommited.set(0, 0, 0);
-        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),
-                new Runnable() {
-                    public void run() {
-                        if (sHud.getAlpha() == 1.0f) {
-                            if (!mFeedAboutToChange) {
-                                setState(STATE_MEDIA_SETS);
-                            }
-                        } else {
-                            sHud.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;
-    }
-
-    public void shutdown() {
-        if (mMediaFeed != null) {
-            mMediaFeed.shutdown();
-        }
-        mContext = null;
-        mBackground = null;
-        sBucketList.clear();
-        mCameraManager = null;
-        mDrawManager = null;
-        mView = null;
-    }
-
-    public void stop() {
-        endSlideshow();
-        mBackground.clear();
-        handleLowMemory();
-    }
-
-    @Override
-    public void generate(RenderView view, RenderView.Lists lists) {
-        lists.updateList.add(this);
-        lists.opaqueList.add(this);
-        mBackground.generate(view, lists);
-        lists.blendedList.add(this);
-        lists.hitTestList.add(this);
-        sHud.generate(view, lists);
-    }
-
-    @Override
-    protected void onSizeChanged() {
-        sHud.setSize(mWidth, mHeight);
-        sHud.setAlpha(1.0f);
-        mBackground.setSize(mWidth, mHeight);
-        mTimeElapsedSinceTransition = 0.0f;
-        if (mView != null) {
-            mView.requestRender();
-        }
-    }
-
-    public int getState() {
-        return mState;
-    }
-
-    public void setState(int state) {
-        boolean feedUnchanged = false;
-        if (mState == state) {
-            feedUnchanged = true;
-        }
-        GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
-        GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
-        oldLayout.mNumRows = layoutInterface.mNumRows;
-        oldLayout.mSpacingX = layoutInterface.mSpacingX;
-        oldLayout.mSpacingY = layoutInterface.mSpacingY;
-        GridCamera camera = mCamera;
-        int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
-        MediaFeed feed = mMediaFeed;
-        boolean performLayout = true;
-        mZoomValue = 1.0f;
-        float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
-        if (yStretch < 1.0f) {
-            yStretch = 1.0f;
-        }
-        switch (state) {
-        case STATE_GRID_VIEW:
-            mTimeElapsedSinceGridViewReady = 0.0f;
-            if (feed != null && feedUnchanged == false) {
-                boolean updatedData = feed.restorePreviousClusteringState();
-                if (updatedData) {
-                    performLayout = false;
-                }
-            }
-            layoutInterface.mNumRows = numMaxRows;
-            layoutInterface.mSpacingX = (int) (10 * Gallery.PIXEL_DENSITY);
-            layoutInterface.mSpacingY = (int) (10 * Gallery.PIXEL_DENSITY);
-            if (mState == STATE_MEDIA_SETS) {
-                // Entering album.
-                mInAlbum = true;
-                MediaSet set = feed.getCurrentSet();
-                int icon = mDrawables.getIconForSet(set, true);
-                if (set != null) {
-                    sHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
-                        public void run() {
-                            if (mFeedAboutToChange) {
-                                return;
-                            }
-                            if (sHud.getAlpha() == 1.0f) {
-                                disableLocationFiltering();
-                                mInputProcessor.clearSelection();
-                                setState(STATE_GRID_VIEW);
-                            } else {
-                                sHud.setAlpha(1.0f);
-                            }
-                        }
-                    });
-                }
-            }
-            if (mState == STATE_FULL_SCREEN) {
-                sHud.getPathBar().popLabel();
-            }
-            break;
-        case STATE_TIMELINE:
-            mTimeElapsedSinceStackViewReady = 0.0f;
-            if (feed != null && feedUnchanged == false) {
-                feed.performClustering();
-                performLayout = false;
-            }
-            disableLocationFiltering();
-            layoutInterface.mNumRows = numMaxRows - 1;
-            layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
-            layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
-            break;
-        case STATE_FULL_SCREEN:
-            layoutInterface.mNumRows = 1;
-            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() {
-                    public void run() {
-                        if (sHud.getAlpha() == 1.0f) {
-                            sHud.swapFullscreenLabel();
-                        }
-                        sHud.setAlpha(1.0f);
-                    }
-                });
-            }
-            break;
-        case STATE_MEDIA_SETS:
-            mTimeElapsedSinceStackViewReady = 0.0f;
-            if (feed != null && feedUnchanged == false) {
-                feed.restorePreviousClusteringState();
-                feed.expandMediaSet(Shared.INVALID);
-                performLayout = false;
-            }
-            disableLocationFiltering();
-            mInputProcessor.clearSelection();
-            layoutInterface.mNumRows = numMaxRows - 1;
-            layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
-            layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
-            if (mInAlbum) {
-                if (mState == STATE_FULL_SCREEN) {
-                    sHud.getPathBar().popLabel();
-                }
-                sHud.getPathBar().popLabel();
-                mInAlbum = false;
-            }
-            break;
-        }
-        mState = state;
-        sHud.onGridStateChanged();
-        if (performLayout && mFeedAboutToChange == false) {
-            onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
-        }
-        if (state != STATE_FULL_SCREEN) {
-            mCamera.moveYTo(0);
-            mCamera.moveZTo(0);
-        }
-    }
-
-    protected void enableLocationFiltering(String label) {
-        if (mLocationFilter == false) {
-            mLocationFilter = true;
-            sHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
-                public void run() {
-                    if (sHud.getAlpha() == 1.0f) {
-                        if (mState == STATE_FULL_SCREEN) {
-                            mInputProcessor.clearSelection();
-                            setState(STATE_GRID_VIEW);
-                        } else {
-                            disableLocationFiltering();
-                        }
-                    } else {
-                        sHud.setAlpha(1.0f);
-                    }
-                }
-            });
-        }
-    }
-
-    protected void disableLocationFiltering() {
-        if (mLocationFilter) {
-            mLocationFilter = false;
-            mMediaFeed.removeFilter();
-            sHud.getPathBar().popLabel();
-        }
-    }
-
-    boolean goBack() {
-        if (mFeedAboutToChange) {
-            return false;
-        }
-        int state = mState;
-        if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
-            if (mLocationFilter) {
-                disableLocationFiltering();
-                setState(STATE_TIMELINE);
-                return true;
-            }
-        }
-        switch (state) {
-        case STATE_GRID_VIEW:
-            setState(STATE_MEDIA_SETS);
-            break;
-        case STATE_TIMELINE:
-            setState(STATE_GRID_VIEW);
-            break;
-        case STATE_FULL_SCREEN:
-            setState(STATE_GRID_VIEW);
-            mInputProcessor.clearSelection();
-            break;
-        default:
-            return false;
-        }
-        return true;
-    }
-
-    public void endSlideshow() {
-        mSlideshowMode = false;
-        if (mWakeLock != null) {
-            if (mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
-            mWakeLock = null;
-        }
-        sHud.setAlpha(1.0f);
-    }
-
-    @Override
-    public void onSensorChanged(RenderView view, SensorEvent event) {
-        mInputProcessor.onSensorChanged(view, event, mState);
-    }
-
-    public DataSource getDataSource() {
-        if (mMediaFeed != null)
-            return mMediaFeed.getDataSource();
-        return null;
-    }
-
-    public void setDataSource(DataSource dataSource) {
-        MediaFeed feed = mMediaFeed;
-        if (feed != null) {
-            feed.shutdown();
-            sDisplayList.clear();
-            mBackground.clear();
-        }
-        mMediaFeed = new MediaFeed(mContext, dataSource, this);
-        mMediaFeed.start();
-    }
-
-    public IndexRange getVisibleRange() {
-        return sVisibleRange;
-    }
-
-    public IndexRange getBufferedVisibleRange() {
-        return sBufferedVisibleRange;
-    }
-
-    public IndexRange getCompleteRange() {
-        return sCompleteRange;
-    }
-
-    private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
-        int retVal = Shared.INVALID;
-        int firstSlotIndex = 0;
-        int lastSlotIndex = 0;
-        IndexRange rangeToUse = sVisibleRange;
-        synchronized (rangeToUse) {
-            firstSlotIndex = rangeToUse.begin;
-            lastSlotIndex = rangeToUse.end;
-        }
-        Pool<Vector3f> pool = sTempVec;
-        float itemWidthBy2 = itemWidth * 0.5f;
-        float itemHeightBy2 = itemHeight * 0.5f;
-        Vector3f position = pool.create();
-        Vector3f deltaAnchorPosition = pool.create();
-        try {
-            deltaAnchorPosition.set(sDeltaAnchorPosition);
-            for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
-                GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
-                if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
-                        position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
-                    retVal = i;
-                    break;
-                }
-            }
-        } finally {
-            pool.delete(deltaAnchorPosition);
-            pool.delete(position);
-        }
-        return retVal;
-    }
-
-    void centerCameraForSlot(int slotIndex, float baseConvergence) {
-        float imageTheta = 0.0f;
-        DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
-        if (displayItem != null) {
-            imageTheta = displayItem.getImageTheta();
-        }
-        mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, sDeltaAnchorPositionUncommited,
-                mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
-    }
-
-    boolean constrainCameraForSlot(int slotIndex) {
-        return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, sDeltaAnchorPosition, mCurrentFocusItemWidth,
-                mCurrentFocusItemHeight);
-    }
-
-    // Called on render thread before rendering.
-    @Override
-    public boolean update(RenderView view, float timeElapsed) {
-        if (mFeedAboutToChange == false) {
-            mTimeElapsedSinceTransition += timeElapsed;
-            mTimeElapsedSinceGridViewReady += timeElapsed;
-            if (mTimeElapsedSinceGridViewReady >= 1.0f) {
-                mTimeElapsedSinceGridViewReady = 1.0f;
-            }
-            mTimeElapsedSinceStackViewReady += timeElapsed;
-            if (mTimeElapsedSinceStackViewReady >= 1.0f) {
-                mTimeElapsedSinceStackViewReady = 1.0f;
-            }
-        } else {
-            mTimeElapsedSinceTransition = 0;
-        }
-        if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
-            HudLayer hud = getHud();
-            hud.getPathBar().setHidden(true);
-            hud.getMenuBar().setHidden(true);
-            if (hud.getMode() != HudLayer.MODE_NORMAL)
-                hud.setMode(HudLayer.MODE_NORMAL);
-        }
-        if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
-            sHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
-        } else {
-            sHud.getPathBar().setAnimatedIcons(null);
-        }
-
-        // In that case, we need to commit the respective Display Items when the
-        // feed was updated.
-        GridCamera camera = mCamera;
-        camera.update(timeElapsed);
-        DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
-        if (anchorDisplayItem != null && !sHud.getTimeBar().isDragged()) {
-            sHud.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);
-        } else {
-            sHud.autoHide(false);
-            sHud.setAlpha(1.0f);
-        }
-        GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
-        int numFullScreenQuads = fullscreenQuads.length;
-        for (int i = 0; i < numFullScreenQuads; ++i) {
-            fullscreenQuads[i].update(timeElapsed);
-        }
-        if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
-            mTimeElapsedSinceView += timeElapsed;
-            if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
-                // time to go to the next slide
-                mTimeElapsedSinceView = 0.0f;
-                changeFocusToNextSlot(0.5f);
-                mCamera.commitMoveInX();
-                mCamera.commitMoveInY();
-            }
-        }
-        if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
-            mCamera.moveYTo(-0.1f);
-            mCamera.commitMoveInY();
-        }
-        boolean dirty = mDrawManager.update(timeElapsed);
-        dirty |= mSlideshowMode;
-        dirty |= mFramesDirty > 0;
-        ++mFrameCount;
-        if (mFramesDirty > 0) {
-            --mFramesDirty;
-        }
-        try {
-            if (mMediaFeed != null && (mMediaFeed.getWaitingForMediaScanner())) {
-                // We limit the drawing of the frame so that the MediaScanner thread can do its work
-                Thread.sleep(200);
-            }
-        } catch (InterruptedException e) {
-
-        }
-        if (sDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
-                || (mTimeElapsedSinceTransition > 0.0f && mTimeElapsedSinceTransition < 1.0f) || mSelectedAlpha != mTargetAlpha
-                // || (mAnimatedFov != mTargetFov)
-                || dirty)
-            return true;
-        else
-            return false;
-    }
-
-    private void computeVisibleRange() {
-        if (mPerformingLayoutChange)
-            return;
-        if (sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited) == false) {
-            sDeltaAnchorPosition.set(sDeltaAnchorPositionUncommited);
-        }
-        mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, sDeltaAnchorPosition, sVisibleRange,
-                sBufferedVisibleRange, sCompleteRange, mState);
-    }
-
-    private void computeVisibleItems() {
-        if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
-            return;
-        }
-        computeVisibleRange();
-        int deltaBegin = sBufferedVisibleRange.begin - sPreviousDataRange.begin;
-        int deltaEnd = sBufferedVisibleRange.end - sPreviousDataRange.end;
-        if (deltaBegin != 0 || deltaEnd != 0) {
-            // The delta has changed, we have to compute the display items again.
-            // We find the intersection range, these slots have not changed at all.
-            int firstVisibleSlotIndex = sBufferedVisibleRange.begin;
-            int lastVisibleSlotIndex = sBufferedVisibleRange.end;
-            sPreviousDataRange.begin = firstVisibleSlotIndex;
-            sPreviousDataRange.end = lastVisibleSlotIndex;
-
-            Pool<Vector3f> pool = sTempVec;
-            Vector3f position = pool.create();
-            Vector3f deltaAnchorPosition = pool.create();
-            try {
-                MediaFeed feed = mMediaFeed;
-                DisplayList displayList = sDisplayList;
-                DisplayItem[] displayItems = sDisplayItems;
-                DisplaySlot[] displaySlots = sDisplaySlots;
-                int numDisplayItems = displayItems.length;
-                int numDisplaySlots = displaySlots.length;
-                ArrayList<MediaItem> visibleItems = sVisibleItems;
-                deltaAnchorPosition.set(sDeltaAnchorPosition);
-                LayoutInterface layout = mLayoutInterface;
-                GridCamera camera = mCamera;
-                for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
-                    GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
-                    MediaSet set = feed.getSetForSlot(i);
-                    int indexIntoSlots = i - firstVisibleSlotIndex;
-
-                    if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
-                        ArrayList<MediaItem> items = set.getItems();
-                        displaySlots[indexIntoSlots].setMediaSet(set);
-                        ArrayList<MediaItem> bestItems = sTempList;
-                        if (mTimeElapsedSinceTransition < 1.0f) {
-                            // We always show the same top thumbnails for a stack of albums
-                            if (mState == STATE_MEDIA_SETS)
-                                ArrayUtils.computeSortedIntersection(items, visibleItems, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
-                            else
-                                ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
-                        }
-                        
-                        int numItemsInSet = set.getNumItems();
-                        int numBestItems = bestItems.size();
-                        int originallyFoundItems = numBestItems;
-                        if (numBestItems < MAX_ITEMS_PER_SLOT) {
-                            int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
-                            for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
-                                MediaItem item = items.get(currItemPos);
-                                if (mTimeElapsedSinceTransition >= 1.0f || !bestItems.contains(item)) {
-                                    bestItems.add(item);
-                                    if (--itemsRemaining == 0) {
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                        numBestItems = bestItems.size();
-                        int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
-                        for (int j = 0; j < numBestItems; ++j) {
-                            if (baseIndex + j >= numDisplayItems) {
-                                break;
-                            }
-                            if (j >= numItemsInSet) {
-                                displayItems[baseIndex + j] = null;
-                            } else {
-                                MediaItem item = bestItems.get(j);
-                                if (item != null) {
-                                    DisplayItem displayItem = displayList.get(item);
-                                    if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
-                                            || (mState == STATE_GRID_VIEW && (mTimeElapsedSinceTransition > 1.0f || j >= originallyFoundItems))) {
-                                        displayItem.set(position, j, false);
-                                        displayItem.commit();
-                                    } else {
-                                        displayList.setPositionAndStackIndex(displayItem, position, j, true);
-                                    }
-                                    displayItems[baseIndex + j] = displayItem;
-                                }
-                            }
-                        }
-                        for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
-                            displayItems[baseIndex + j] = null;
-                        }
-                        bestItems.clear();
-                    }
-                }
-                if (mFeedChanged) {
-                    mFeedChanged = false;
-                    if (mInputProcessor != null && mState == STATE_FULL_SCREEN) {
-                        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
-                        if (currentSelectedSlot > sCompleteRange.end)
-                            currentSelectedSlot = sCompleteRange.end;
-                        mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
-                    }
-                    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 (mRequestFocusContentUri != null) {
-                        // We have to find the item that has this contentUri
-                        if (mState == STATE_FULL_SCREEN) {
-                            int numSlots = sCompleteRange.end;
-                            for (int i = 0; i < numSlots; ++i) {
-                                MediaSet set = feed.getSetForSlot(i);
-                                ArrayList<MediaItem> items = set.getItems();
-                                int numItems = items.size();
-                                for (int j = 0; j < numItems; ++j) {
-                                    if (items.get(j).mContentUri.equals(mRequestFocusContentUri)) {
-                                        mInputProcessor.setCurrentSelectedSlot(i);
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                        mRequestFocusContentUri = null;
-                    }
-                }
-            } finally {
-                pool.delete(position);
-                pool.delete(deltaAnchorPosition);
-            }
-            // We keep upto 400 thumbnails in memory.
-            int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
-            int startMemoryRange = (sBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
-            if (mStartMemoryRange != startMemoryRange) {
-                mStartMemoryRange = startMemoryRange;
-                clearUnusedThumbnails();
-            }
-        }
-    }
-
-    @Override
-    public void handleLowMemory() {
-        clearUnusedThumbnails();
-        GridDrawables.sStringTextureTable.clear();
-        mBackground.clearCache();
-    }
-
-    // This method can be potentially expensive
-    public void clearUnusedThumbnails() {
-        sDisplayList.clearExcept(sDisplayItems);
-    }
-
-    @Override
-    public void onSurfaceCreated(RenderView view, GL11 gl) {
-        sDisplayList.clear();
-        sHud.clear();
-        sHud.reset();
-        GridDrawables.sStringTextureTable.clear();
-        mDrawables.onSurfaceCreated(view, gl);
-        mBackground.clear();
-    }
-
-    @Override
-    public void onSurfaceChanged(RenderView view, int width, int height) {
-        mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
-        view.setFov(mCamera.mFov);
-        setState(mState);
-    }
-
-    // Renders the node in a given pass.
-    public void renderOpaque(RenderView view, GL11 gl) {
-        GridCamera camera = mCamera;
-        int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
-        computeVisibleItems();
-        
-        gl.glMatrixMode(GL11.GL_MODELVIEW);
-        gl.glLoadIdentity();
-        GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
-                camera.mUpX, camera.mUpY, camera.mUpZ);
-        view.setAlpha(1.0f);
-        if (mSelectedAlpha != 1.0f) {
-            gl.glEnable(GL11.GL_BLEND);
-            gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
-            view.setAlpha(mSelectedAlpha);
-        }
-        if (selectedSlotIndex != Shared.INVALID) {
-            mTargetAlpha = 0.0f;
-        } else {
-            mTargetAlpha = 1.0f;
-        }
-        mDrawManager.prepareDraw(sBufferedVisibleRange, sVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
-                mInputProcessor.isFocusItemPressed());
-        if (mSelectedAlpha != 0.0f) {
-            mDrawManager.drawThumbnails(view, gl, mState);
-        }
-        if (mSelectedAlpha != 1.0f) {
-            gl.glDisable(GL11.GL_BLEND);
-        }
-        // We draw the selected slotIndex.
-        if (selectedSlotIndex != Shared.INVALID) {
-            mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
-            mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
-            mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
-        }
-        view.setAlpha(mSelectedAlpha);
-    }
-
-    public void renderBlended(RenderView view, GL11 gl) {
-        // We draw the placeholder for all visible slots.
-        if (sHud != null && mDrawManager != null) {
-            mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, sHud.getMode(), mTimeElapsedSinceStackViewReady,
-                    mTimeElapsedSinceGridViewReady, sBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
-                            || mMediaFeed.isLoading());
-        }
-    }
-
-    public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
-        if (mPerformingLayoutChange || !sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited)) {
-            return;
-        }
-
-        mTimeElapsedSinceTransition = 0.0f;
-        mPerformingLayoutChange = true;
-        LayoutInterface layout = mLayoutInterface;
-        if (oldLayout == null) {
-            oldLayout = sfullScreenLayoutInterface;
-        }
-        GridCamera camera = mCamera;
-        if (currentAnchorSlotIndex == Shared.INVALID) {
-            currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
-            if (mCurrentExpandedSlot != Shared.INVALID) {
-                currentAnchorSlotIndex = mCurrentExpandedSlot;
-            }
-            int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
-            if (selectedSlotIndex != Shared.INVALID) {
-                currentAnchorSlotIndex = selectedSlotIndex;
-            }
-        }
-        if (newAnchorSlotIndex == Shared.INVALID) {
-            newAnchorSlotIndex = currentAnchorSlotIndex;
-        }
-        int itemHeight = camera.mItemHeight;
-        int itemWidth = camera.mItemWidth;
-        Pool<Vector3f> pool = sTempVec;
-        Vector3f deltaAnchorPosition = pool.create();
-        Vector3f currentSlotPosition = pool.create();
-        try {
-            deltaAnchorPosition.set(0, 0, 0);
-            if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
-                layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
-                oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
-                currentSlotPosition.subtract(sDeltaAnchorPosition);
-                deltaAnchorPosition.subtract(currentSlotPosition);
-                deltaAnchorPosition.y = 0;
-                deltaAnchorPosition.z = 0;
-            }
-            sDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
-        } finally {
-            pool.delete(deltaAnchorPosition);
-            pool.delete(currentSlotPosition);
-        }
-        centerCameraForSlot(newAnchorSlotIndex, 1.0f);
-        mCurrentExpandedSlot = Shared.INVALID;
-        
-        // Force recompute of visible items and their positions.
-        ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
-        ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
-        ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
-        forceRecomputeVisibleRange();
-        mPerformingLayoutChange = false;
-    }
-
-    private void forceRecomputeVisibleRange() {
-        sPreviousDataRange.begin = Shared.INVALID;
-        sPreviousDataRange.end = Shared.INVALID;
-        if (mView != null) {
-            mView.requestRender();
-        }
-    }
-
-    // called on background thread
-    public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
-        if (!needsLayout) {
-            mFeedChanged = true;
-            forceRecomputeVisibleRange();
-            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
-                sHud.setFeed(feed, mState, needsLayout);
-            return;
-        }
-
-        while (mPerformingLayoutChange == true) {
-            Thread.yield();
-        }
-        if (mState == STATE_GRID_VIEW) {
-            if (sHud != null) {
-                MediaSet set = feed.getCurrentSet();
-                if (set != null && !mLocationFilter)
-                    sHud.getPathBar().changeLabel(set.mNoCountTitleString);
-            }
-        }
-        DisplayItem[] displayItems = sDisplayItems;
-        int firstBufferedVisibleSlotIndex = sBufferedVisibleRange.begin;
-        int lastBufferedVisibleSlotIndex = sBufferedVisibleRange.end;
-        int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
-        if (mCurrentExpandedSlot != Shared.INVALID) {
-            currentlyVisibleSlotIndex = mCurrentExpandedSlot;
-        }
-        MediaItem anchorItem = null;
-        ArrayList<MediaItem> visibleItems = sVisibleItems;
-        visibleItems.clear();
-        visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
-        if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
-                && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
-            int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
-            for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
-                DisplayItem displayItem = displayItems[baseIndex + i];
-                if (displayItem != null) {
-                    if (anchorItem == null) {
-                        anchorItem = displayItem.mItemRef;
-                    }
-                    visibleItems.add(displayItem.mItemRef);
-                }
-            }
-        }
-        // We want to add items from the middle.
-        int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
-        int midPoint = (lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex) / 2;
-        int length = displayItems.length;
-        for (int i = 0; i < numItems; ++i) {
-            int index = midPoint + Shared.midPointIterator(i);
-            int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
-            if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
-                for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
-                    DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
-                    if (displayItem != null) {
-                        MediaItem item = displayItem.mItemRef;
-                        if (item != anchorItem) {
-                            visibleItems.add(item);
-                        }
-                    }
-                }
-            }
-        }
-        int newSlotIndex = Shared.INVALID;
-        if (anchorItem != null) {
-            // We try to find the anchor item in the new feed.
-            int numSlots = feed.getNumSlots();
-            for (int i = 0; i < numSlots; ++i) {
-                MediaSet set = feed.getSetForSlot(i);
-                if (set != null && set.containsItem(anchorItem)) {
-                    newSlotIndex = i;
-                    break;
-                }
-            }
-        }
-
-        if (anchorItem != null && newSlotIndex == Shared.INVALID) {
-            int numSlots = feed.getNumSlots();
-            MediaSet parentSet = anchorItem.mParentMediaSet;
-            for (int i = 0; i < numSlots; ++i) {
-                MediaSet set = feed.getSetForSlot(i);
-                if (set != null && set.mId == parentSet.mId) {
-                    newSlotIndex = i;
-                    break;
-                }
-            }
-        }
-        
-        // We must create a new display store now since the data has changed.
-        if (newSlotIndex != Shared.INVALID) {
-            if (mState == STATE_MEDIA_SETS) {
-                sDisplayList.clearExcept(displayItems);
-            }
-            onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
-        } else {
-            forceRecomputeVisibleRange();
-        }
-        mCurrentExpandedSlot = Shared.INVALID;
-        mFeedAboutToChange = false;
-        mFeedChanged = true;
-        if (feed != null) {
-            if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
-                sHud.setFeed(feed, mState, needsLayout);
-        }
-        if (mView != null) {
-            mView.requestRender();
-        }
-    }
-    
-    public DisplayItem getRepresentativeDisplayItem() {
-        int slotIndex = Shared.INVALID;
-        if (mInputProcessor != null) {
-            slotIndex = mInputProcessor.getCurrentFocusSlot();
-        }
-        if (slotIndex == Shared.INVALID) {
-            slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
-        }
-        int index = (slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
-        if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
-            return sDisplayItems[index];
-        } else {
-            return null;
-        }
-    }
-
-    public DisplayItem getAnchorDisplayItem(int type) {
-        int slotIndex = getAnchorSlotIndex(type);
-        return sDisplayItems[(slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT];
-    }
-
-    public float getScrollPosition() {
-        return (mCamera.mLookAtX * mCamera.mScale + sDeltaAnchorPosition.x); // in
-        // pixels
-    }
-
-    public DisplayItem getDisplayItemForScrollPosition(float posX) {
-        Pool<Vector3f> pool = sTempVecAlt;
-        MediaFeed feed = mMediaFeed;
-        int itemWidth = mCamera.mItemWidth;
-        int itemHeight = mCamera.mItemHeight;
-        GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
-        float absolutePosX = posX;
-        int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
-        int right = feed == null ? 0 : (int) (feed.getNumSlots());
-        int retSlot = left;
-        Vector3f position = pool.create();
-        try {
-            for (int i = left; i < right; ++i) {
-                gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
-                retSlot = i;
-                if (position.x >= absolutePosX) {
-                    break;
-                }
-            }
-        } finally {
-            pool.delete(position);
-        }
-        if (mFeedAboutToChange) {
-            return null;
-        }
-        right = feed == null ? 0 : feed.getNumSlots();
-        if (right == 0) {
-            return null;
-        }
-
-        if (retSlot >= right)
-            retSlot = right - 1;
-        MediaSet set = feed.getSetForSlot(retSlot);
-        if (set != null) {
-            ArrayList<MediaItem> items = set.getItems();
-            if (items != null && set.getNumItems() > 0) {
-                return (sDisplayList.get(items.get(0)));
-            }
-        }
-        return null;
-    }
-
-    // Returns the top left-most item.
-    public int getAnchorSlotIndex(int anchorType) {
-        int retVal = 0;
-        switch (anchorType) {
-        case ANCHOR_LEFT:
-            retVal = sVisibleRange.begin;
-            break;
-        case ANCHOR_RIGHT:
-            retVal = sVisibleRange.end;
-            break;
-        case ANCHOR_CENTER:
-            retVal = (sVisibleRange.begin + sVisibleRange.end) / 2;
-            break;
-        }
-        return retVal;
-    }
-
-    DisplayItem getDisplayItemForSlotId(int slotId) {
-        int index = slotId - sBufferedVisibleRange.begin;
-        if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
-            return sDisplayItems[index * MAX_ITEMS_PER_SLOT];
-        }
-        return null;
-    }
-
-    boolean changeFocusToNextSlot(float convergence) {
-        int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
-        boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
-        if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
-            endSlideshow();
-            sHud.setAlpha(1.0f);
-        }
-        return retVal;
-    }
-
-    boolean changeFocusToSlot(int slotId, float convergence) {
-        mZoomValue = 1.0f;
-        int index = slotId - sBufferedVisibleRange.begin;
-        if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
-            DisplayItem displayItem = sDisplayItems[index * MAX_ITEMS_PER_SLOT];
-            if (displayItem != null) {
-                MediaItem item = displayItem.mItemRef;
-                sHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
-                if (slotId != Shared.INVALID && slotId <= sCompleteRange.end) {
-                    mInputProcessor.setCurrentFocusSlot(slotId);
-                    centerCameraForSlot(slotId, convergence);
-                    return true;
-                } else {
-                    centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
-                    return false;
-                }
-            }
-        }
-        return false;
-    }
-
-    boolean changeFocusToPreviousSlot(float convergence) {
-        return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
-    }
-
-    public ArrayList<MediaBucket> getSelectedBuckets() {
-        return sBucketList.get();
-    }
-
-    public void selectAll() {
-        if (mState != STATE_FULL_SCREEN) {
-            int numSlots = sCompleteRange.end + 1;
-            for (int i = 0; i < numSlots; ++i) {
-                addSlotToSelectedItems(i, false, false);
-            }
-            updateCountOfSelectedItems();
-        } else {
-            addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
-        }
-    }
-
-    public void deselectOrCancelSelectMode() {
-        if (sBucketList.size() == 0) {
-            sHud.cancelSelection();
-        } else {
-            sBucketList.clear();
-            updateCountOfSelectedItems();
-        }
-    }
-
-    public void deselectAll() {
-        sHud.cancelSelection();
-        sBucketList.clear();
-        updateCountOfSelectedItems();
-    }
-
-    public void deleteSelection() {
-        // Delete the selection and exit selection mode.
-        mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
-        deselectAll();
-
-        // If the current set is now empty, return to the parent set.
-        if (sCompleteRange.isEmpty()) {
-            goBack(); // TODO(venkat): This does not work most of the time, can you take a look?
-        }
-    }
-
-    void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
-        if (mFeedAboutToChange == false) {
-            MediaFeed feed = mMediaFeed;
-            sBucketList.add(slotId, feed, removeIfAlreadyAdded);
-            if (updateCount) {
-                updateCountOfSelectedItems();
-                if (sBucketList.size() == 0)
-                    deselectAll();
-            }
-        }
-    }
-
-    private void updateCountOfSelectedItems() {
-        sHud.updateNumItemsSelected(sBucketList.size());
-    }
-
-    public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
-        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * Gallery.PIXEL_DENSITY), mCamera.mItemHeight
-                + (int) (100 * Gallery.PIXEL_DENSITY));
-    }
-
-    public int getSlotIndexForScreenPosition(int posX, int posY) {
-        return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
-    }
-
-    private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
-        Pool<Vector3f> pool = sTempVec;
-        int retVal = 0;
-        Vector3f worldPos = pool.create();
-        try {
-            GridCamera camera = mCamera;
-            camera.convertToCameraSpace(posX, posY, 0, worldPos);
-            // slots are expressed in pixels as well
-            worldPos.x *= camera.mScale;
-            worldPos.y *= camera.mScale;
-            // we ignore z
-            retVal = hitTest(worldPos, itemWidth, itemHeight);
-        } finally {
-            pool.delete(worldPos);
-        }
-        return retVal;
-    }
-
-    public boolean tapGesture(int slotIndex, boolean metadata) {
-        MediaFeed feed = mMediaFeed;
-        if (!feed.isClustered()) {
-            // It is not clustering.
-            if (!feed.hasExpandedMediaSet()) {
-                if (feed.canExpandSet(slotIndex)) {
-                    mCurrentExpandedSlot = slotIndex;
-                    feed.expandMediaSet(slotIndex);
-                    setState(STATE_GRID_VIEW);
-                }
-                return false;
-            } else {
-                return true;
-            }
-        } else {
-            // Select a cluster, and recompute a new cluster within this cluster.
-            mCurrentExpandedSlot = slotIndex;
-            goBack();
-            if (metadata) {
-                DisplaySlot slot = sDisplaySlots[slotIndex - sBufferedVisibleRange.begin];
-                if (slot.hasValidLocation()) {
-                    MediaSet set = slot.getMediaSet();
-                    if (set.mReverseGeocodedLocation != null) {
-                        enableLocationFiltering(set.mReverseGeocodedLocation);
-                    }
-                    feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude, set.mMaxLonLongitude));
-                }
-            }
-            return false;
-        }
-    }
-
-    public void onTimeChanged(TimeBar timebar) {
-        if (mFeedAboutToChange) {
-            return;
-        }
-        // TODO lot of optimization possible here
-        MediaItem item = timebar.getItem();
-        MediaFeed feed = mMediaFeed;
-        int numSlots = feed.getNumSlots();
-        for (int i = 0; i < numSlots; ++i) {
-            MediaSet set = feed.getSetForSlot(i);
-            if (set == null) {
-                return;
-            }
-            ArrayList<MediaItem> items = set.getItems();
-            if (items == null || set.getNumItems() == 0) {
-                return;
-            }
-            if (items.contains(item)) {
-                centerCameraForSlot(i, 1.0f);
-                break;
-            }
-        }
-    }
-
-    public void onFeedAboutToChange(MediaFeed feed) {
-        mFeedAboutToChange = true;
-        mTimeElapsedSinceTransition = 0;
-    }
-
-    public void startSlideshow() {
-        endSlideshow();
-        mSlideshowMode = true;
-        mZoomValue = 1.0f;
-        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
-        mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
-        sHud.setAlpha(0);
-        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
-        mWakeLock.acquire();
-    }
-
-    public void enterSelectionMode() {
-        mSlideshowMode = false;
-        sHud.enterSelectionMode();
-        int currentSlot = mInputProcessor.getCurrentSelectedSlot();
-        if (currentSlot == Shared.INVALID) {
-            currentSlot = mInputProcessor.getCurrentFocusSlot();
-        }
-        addSlotToSelectedItems(currentSlot, false, true);
-    }
-
-    private float getFillScreenZoomValue() {
-        return GridCameraManager.getFillScreenZoomValue(mCamera, sTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
-    }
-
-    public void zoomInToSelectedItem() {
-        mSlideshowMode = false;
-        float potentialZoomValue = getFillScreenZoomValue();
-        if (mZoomValue < potentialZoomValue) {
-            mZoomValue = potentialZoomValue;
-        } else {
-            mZoomValue *= 3.0f;
-        }
-        if (mZoomValue > 6.0f) {
-            mZoomValue = 6.0f;
-        }
-        sHud.setAlpha(1.0f);
-        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
-    }
-
-    public void zoomOutFromSelectedItem() {
-        mSlideshowMode = false;
-        if (mZoomValue == getFillScreenZoomValue()) {
-            mZoomValue = 1.0f;
-        } else {
-            mZoomValue /= 3.0f;
-        }
-        if (mZoomValue < 1.0f) {
-            mZoomValue = 1.0f;
-        }
-        sHud.setAlpha(1.0f);
-        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
-    }
-
-    public void rotateSelectedItems(float f) {
-        MediaBucketList bucketList = sBucketList;
-        ArrayList<MediaBucket> mediaBuckets = bucketList.get();
-        DisplayList displayList = sDisplayList;
-        int numBuckets = mediaBuckets.size();
-        for (int i = 0; i < numBuckets; ++i) {
-            MediaBucket bucket = mediaBuckets.get(i);
-            ArrayList<MediaItem> mediaItems = bucket.mediaItems;
-            if (mediaItems != null) {
-                int numMediaItems = mediaItems.size();
-                for (int j = 0; j < numMediaItems; ++j) {
-                    MediaItem item = mediaItems.get(j);
-                    DisplayItem displayItem = displayList.get(item);
-                    displayItem.rotateImageBy(f);
-                    displayList.addToAnimatables(displayItem);
-                }
-            }
-        }
-        if (mState == STATE_FULL_SCREEN) {
-            centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
-        }
-        mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
-        // we recreate these displayitems from the cache
-    }
-
-    public void cropSelectedItem() {
-
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        return mInputProcessor.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mInputProcessor != null)
-            return mInputProcessor.onKeyDown(keyCode, event, mState);
-        return false;
-    }
-
-    public boolean inSlideShowMode() {
-        return mSlideshowMode;
-    }
-
-    public boolean noDeleteMode() {
-        return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
-    }
-
-    public float getZoomValue() {
-        return mZoomValue;
-    }
-
-    public boolean feedAboutToChange() {
-        return mFeedAboutToChange;
-    }
-
-    public boolean isInAlbumMode() {
-        return mInAlbum;
-    }
-
-    public Vector3f getDeltaAnchorPosition() {
-        return sDeltaAnchorPosition;
-    }
-
-    public int getExpandedSlot() {
-        return mCurrentExpandedSlot;
-    }
-
-    public GridLayoutInterface getLayoutInterface() {
-        return (GridLayoutInterface) mLayoutInterface;
-    }
-
-    public void setZoomValue(float f) {
-        mZoomValue = f;
-        centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
-    }
-
-    public void setPickIntent(boolean b) {
-        mPickIntent = b;
-        sHud.getPathBar().popLabel();
-        sHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
-                new Runnable() {
-                    public void run() {
-                        if (sHud.getAlpha() == 1.0f) {
-                            if (!mFeedAboutToChange) {
-                                setState(STATE_MEDIA_SETS);
-                            }
-                        } else {
-                            sHud.setAlpha(1.0f);
-                        }
-                    }
-                });
-    }
-
-    public boolean getPickIntent() {
-        return mPickIntent;
-    }
-
-    public void setViewIntent(boolean b, final String setName) {
-        mViewIntent = b;
-        if (b) {
-            mMediaFeed.expandMediaSet(0);
-            setState(STATE_GRID_VIEW);
-            // We need to make sure we haven't pushed the same label twice
-            if (sHud.getPathBar().getNumLevels() == 1) {
-                sHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
-                    public void run() {
-                        if (mFeedAboutToChange) {
-                            return;
-                        }
-                        if (sHud.getAlpha() == 1.0f) {
-                        disableLocationFiltering();
-                        if (mInputProcessor != null)
-                            mInputProcessor.clearSelection();
-                        setState(STATE_GRID_VIEW);
-                        } else {
-                            sHud.setAlpha(1.0f);
-                        }
-                    }
-                });
-            }
-        }
-    }
-
-    public boolean getViewIntent() {
-        return mViewIntent;
-    }
-
-    public void setSingleImage(boolean noDeleteMode) {
-        mNoDeleteMode = noDeleteMode;
-        mInputProcessor.setCurrentSelectedSlot(0);
-    }
-
-    public MediaFeed getFeed() {
-        return mMediaFeed;
-    }
-
-    public void markDirty(int numFrames) {
-        mFramesDirty = numFrames;
-    }
-
-    public void focusItem(String contentUri) {
-        mRequestFocusContentUri = contentUri;
-        mMediaFeed.updateListener(false);
-    }
+       public static final int STATE_MEDIA_SETS = 0;
+       public static final int STATE_GRID_VIEW = 1;
+       public static final int STATE_FULL_SCREEN = 2;
+       public static final int STATE_TIMELINE = 3;
+
+       public static final int ANCHOR_LEFT = 0;
+       public static final int ANCHOR_RIGHT = 1;
+       public static final int ANCHOR_CENTER = 2;
+
+       public static final int MAX_ITEMS_PER_SLOT = 12;
+       public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
+       public static final int MAX_DISPLAY_SLOTS = 96;
+       public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;
+
+       private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
+
+       private static HudLayer sHud;
+       private int mState;
+       private static final IndexRange sBufferedVisibleRange = new IndexRange();
+       private static final IndexRange sVisibleRange = new IndexRange();
+       private static final IndexRange sPreviousDataRange = new IndexRange();
+       private static final IndexRange sCompleteRange = new IndexRange();
+
+       private static final Pool<Vector3f> sTempVec;
+       private static final Pool<Vector3f> sTempVecAlt;
+       static {
+               Vector3f[] vectorPool = new Vector3f[128];
+               int length = vectorPool.length;
+               for (int i = 0; i < length; ++i) {
+                       vectorPool[i] = new Vector3f();
+               }
+               Vector3f[] vectorPoolRenderThread = new Vector3f[128];
+               length = vectorPoolRenderThread.length;
+               for (int i = 0; i < length; ++i) {
+                       vectorPoolRenderThread[i] = new Vector3f();
+               }
+               sTempVec = new Pool<Vector3f>(vectorPool);
+               sTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
+       }
+
+       private static final ArrayList<MediaItem> sTempList = new ArrayList<MediaItem>();
+       private static final MediaItem[] sTempHash = new MediaItem[64];
+
+       private static final Vector3f sDeltaAnchorPositionUncommited = new Vector3f();
+       private static Vector3f sDeltaAnchorPosition = new Vector3f();
+
+       // The display primitives.
+       private GridDrawables mDrawables;
+       private float mSelectedAlpha = 0.0f;
+       private float mTargetAlpha = 0.0f;
+
+       private GridCamera mCamera;
+       private GridCameraManager mCameraManager;
+       private GridDrawManager mDrawManager;
+       private GridInputProcessor mInputProcessor;
+
+       private boolean mFeedAboutToChange;
+       private boolean mPerformingLayoutChange;
+       private boolean mFeedChanged;
+
+       private final LayoutInterface mLayoutInterface;
+       private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);
+
+       private MediaFeed mMediaFeed;
+       private boolean mInAlbum = false;
+       private int mCurrentExpandedSlot;
+
+       private static final DisplayList sDisplayList = new DisplayList();
+       private static final DisplayItem[] sDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
+       private static final DisplaySlot[] sDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
+       private static ArrayList<MediaItem> sVisibleItems;
+
+       private float mTimeElapsedSinceTransition;
+       private BackgroundLayer mBackground;
+       private boolean mLocationFilter;
+       private float mZoomValue = 1.0f;
+       private float mCurrentFocusItemWidth = 1.0f;
+       private float mCurrentFocusItemHeight = 1.0f;
+       private float mTimeElapsedSinceGridViewReady = 0.0f;
+
+       private boolean mSlideshowMode;
+       private boolean mNoDeleteMode = false;
+       private float mTimeElapsedSinceView;
+       private static final MediaBucketList sBucketList = new MediaBucketList();
+       private float mTimeElapsedSinceStackViewReady;
+
+       private Context mContext;
+       private RenderView mView;
+       private boolean mPickIntent;
+       private boolean mViewIntent;
+       private WakeLock mWakeLock;
+       private int mStartMemoryRange;
+       private int mFramesDirty;
+       private String mRequestFocusContentUri;
+       private int mFrameCount;
+       private boolean mNeedsInit;
+
+       public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
+               mBackground = new BackgroundLayer(this);
+               mContext = context;
+               mView = view;
+               mNeedsInit = true;
+
+               DisplaySlot[] displaySlots = sDisplaySlots;
+               for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
+                       DisplaySlot slot = new DisplaySlot();
+                       displaySlots[i] = slot;
+               }
+               mLayoutInterface = layoutInterface;
+               mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
+               mDrawables = new GridDrawables(itemWidth, itemHeight);
+               sBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
+               sVisibleRange.set(Shared.INVALID, Shared.INVALID);
+               sCompleteRange.set(Shared.INVALID, Shared.INVALID);
+               sPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
+               sDeltaAnchorPosition.set(0, 0, 0);
+               sDeltaAnchorPositionUncommited.set(0, 0, 0);
+               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),
+                       new Runnable() {
+                               public void run() {
+                                       if (sHud.getAlpha() == 1.0f) {
+                                               if (!mFeedAboutToChange) {
+                                                       setState(STATE_MEDIA_SETS);
+                                               }
+                                       } else {
+                                               sHud.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);
+       }
+
+       public HudLayer getHud() {
+               return sHud;
+       }
+
+       public void shutdown() {
+               if (mMediaFeed != null) {
+                       mMediaFeed.shutdown();
+               }
+               mContext = null;
+               mBackground = null;
+               sBucketList.clear();
+               mView = null;
+       }
+
+       public void stop() {
+               endSlideshow();
+               mBackground.clear();
+               handleLowMemory();
+       }
+
+       @Override
+       public void generate(RenderView view, RenderView.Lists lists) {
+               lists.updateList.add(this);
+               lists.opaqueList.add(this);
+               mBackground.generate(view, lists);
+               lists.blendedList.add(this);
+               lists.hitTestList.add(this);
+               sHud.generate(view, lists);
+       }
+
+       @Override
+       protected void onSizeChanged() {
+               sHud.setSize(mWidth, mHeight);
+               sHud.setAlpha(1.0f);
+               mBackground.setSize(mWidth, mHeight);
+               mTimeElapsedSinceTransition = 0.0f;
+               if (mView != null) {
+                       mView.requestRender();
+               }
+       }
+
+       public int getState() {
+               return mState;
+       }
+
+       public void setState(int state) {
+               boolean feedUnchanged = false;
+               if (mState == state) {
+                       feedUnchanged = true;
+               }
+               GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
+               GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
+               oldLayout.mNumRows = layoutInterface.mNumRows;
+               oldLayout.mSpacingX = layoutInterface.mSpacingX;
+               oldLayout.mSpacingY = layoutInterface.mSpacingY;
+               GridCamera camera = mCamera;
+               int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
+               MediaFeed feed = mMediaFeed;
+               boolean performLayout = true;
+               mZoomValue = 1.0f;
+               float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
+               if (yStretch < 1.0f) {
+                       yStretch = 1.0f;
+               }
+               switch (state) {
+               case STATE_GRID_VIEW:
+                       mTimeElapsedSinceGridViewReady = 0.0f;
+                       if (feed != null && feedUnchanged == false) {
+                               boolean updatedData = feed.restorePreviousClusteringState();
+                               if (updatedData) {
+                                       performLayout = false;
+                               }
+                       }
+                       layoutInterface.mNumRows = numMaxRows;
+                       layoutInterface.mSpacingX = (int) (10 * Gallery.PIXEL_DENSITY);
+                       layoutInterface.mSpacingY = (int) (10 * Gallery.PIXEL_DENSITY);
+                       if (mState == STATE_MEDIA_SETS) {
+                               // Entering album.
+                               mInAlbum = true;
+                               MediaSet set = feed.getCurrentSet();
+                               int icon = mDrawables.getIconForSet(set, true);
+                               if (set != null) {
+                                       sHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
+                                               public void run() {
+                                                       if (mFeedAboutToChange) {
+                                                               return;
+                                                       }
+                                                       if (sHud.getAlpha() == 1.0f) {
+                                                               disableLocationFiltering();
+                                                               mInputProcessor.clearSelection();
+                                                               setState(STATE_GRID_VIEW);
+                                                       } else {
+                                                               sHud.setAlpha(1.0f);
+                                                       }
+                                               }
+                                       });
+                               }
+                       }
+                       if (mState == STATE_FULL_SCREEN) {
+                               sHud.getPathBar().popLabel();
+                       }
+                       break;
+               case STATE_TIMELINE:
+                       mTimeElapsedSinceStackViewReady = 0.0f;
+                       if (feed != null && feedUnchanged == false) {
+                               feed.performClustering();
+                               performLayout = false;
+                       }
+                       disableLocationFiltering();
+                       layoutInterface.mNumRows = numMaxRows - 1;
+                       layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
+                       layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
+                       break;
+               case STATE_FULL_SCREEN:
+                       layoutInterface.mNumRows = 1;
+                       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() {
+                                       public void run() {
+                                               if (sHud.getAlpha() == 1.0f) {
+                                                       sHud.swapFullscreenLabel();
+                                               }
+                                               sHud.setAlpha(1.0f);
+                                       }
+                               });
+                       }
+                       break;
+               case STATE_MEDIA_SETS:
+                       mTimeElapsedSinceStackViewReady = 0.0f;
+                       if (feed != null && feedUnchanged == false) {
+                               feed.restorePreviousClusteringState();
+                               feed.expandMediaSet(Shared.INVALID);
+                               performLayout = false;
+                       }
+                       disableLocationFiltering();
+                       mInputProcessor.clearSelection();
+                       layoutInterface.mNumRows = numMaxRows - 1;
+                       layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
+                       layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
+                       if (mInAlbum) {
+                               if (mState == STATE_FULL_SCREEN) {
+                                       sHud.getPathBar().popLabel();
+                               }
+                               sHud.getPathBar().popLabel();
+                               mInAlbum = false;
+                       }
+                       break;
+               }
+               mState = state;
+               sHud.onGridStateChanged();
+               if (performLayout && mFeedAboutToChange == false) {
+                       onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
+               }
+               if (state != STATE_FULL_SCREEN) {
+                       mCamera.moveYTo(0);
+                       mCamera.moveZTo(0);
+               }
+       }
+
+       protected void enableLocationFiltering(String label) {
+               if (mLocationFilter == false) {
+                       mLocationFilter = true;
+                       sHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
+                               public void run() {
+                                       if (sHud.getAlpha() == 1.0f) {
+                                               if (mState == STATE_FULL_SCREEN) {
+                                                       mInputProcessor.clearSelection();
+                                                       setState(STATE_GRID_VIEW);
+                                               } else {
+                                                       disableLocationFiltering();
+                                               }
+                                       } else {
+                                               sHud.setAlpha(1.0f);
+                                       }
+                               }
+                       });
+               }
+       }
+
+       protected void disableLocationFiltering() {
+               if (mLocationFilter) {
+                       mLocationFilter = false;
+                       mMediaFeed.removeFilter();
+                       sHud.getPathBar().popLabel();
+               }
+       }
+
+       boolean goBack() {
+               if (mFeedAboutToChange) {
+                       return false;
+               }
+               int state = mState;
+               if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
+                       if (mLocationFilter) {
+                               disableLocationFiltering();
+                               setState(STATE_TIMELINE);
+                               return true;
+                       }
+               }
+               switch (state) {
+               case STATE_GRID_VIEW:
+                       setState(STATE_MEDIA_SETS);
+                       break;
+               case STATE_TIMELINE:
+                       setState(STATE_GRID_VIEW);
+                       break;
+               case STATE_FULL_SCREEN:
+                       setState(STATE_GRID_VIEW);
+                       mInputProcessor.clearSelection();
+                       break;
+               default:
+                       return false;
+               }
+               return true;
+       }
+
+       public void endSlideshow() {
+               mSlideshowMode = false;
+               if (mWakeLock != null) {
+                       if (mWakeLock.isHeld()) {
+                               mWakeLock.release();
+                       }
+                       mWakeLock = null;
+               }
+               sHud.setAlpha(1.0f);
+       }
+
+       @Override
+       public void onSensorChanged(RenderView view, SensorEvent event) {
+               mInputProcessor.onSensorChanged(view, event, mState);
+       }
+
+       public DataSource getDataSource() {
+               if (mMediaFeed != null)
+                       return mMediaFeed.getDataSource();
+               return null;
+       }
+
+       public void setDataSource(DataSource dataSource) {
+               MediaFeed feed = mMediaFeed;
+               if (feed != null) {
+                       feed.shutdown();
+                       sDisplayList.clear();
+                       mBackground.clear();
+               }
+               mMediaFeed = new MediaFeed(mContext, dataSource, this);
+               mMediaFeed.start();
+       }
+
+       public IndexRange getVisibleRange() {
+               return sVisibleRange;
+       }
+
+       public IndexRange getBufferedVisibleRange() {
+               return sBufferedVisibleRange;
+       }
+
+       public IndexRange getCompleteRange() {
+               return sCompleteRange;
+       }
+
+       private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
+               int retVal = Shared.INVALID;
+               int firstSlotIndex = 0;
+               int lastSlotIndex = 0;
+               IndexRange rangeToUse = sVisibleRange;
+               synchronized (rangeToUse) {
+                       firstSlotIndex = rangeToUse.begin;
+                       lastSlotIndex = rangeToUse.end;
+               }
+               Pool<Vector3f> pool = sTempVec;
+               float itemWidthBy2 = itemWidth * 0.5f;
+               float itemHeightBy2 = itemHeight * 0.5f;
+               Vector3f position = pool.create();
+               Vector3f deltaAnchorPosition = pool.create();
+               try {
+                       deltaAnchorPosition.set(sDeltaAnchorPosition);
+                       for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
+                               GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
+                               if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
+                                       position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
+                                       retVal = i;
+                                       break;
+                               }
+                       }
+               } finally {
+                       pool.delete(deltaAnchorPosition);
+                       pool.delete(position);
+               }
+               return retVal;
+       }
+
+       void centerCameraForSlot(int slotIndex, float baseConvergence) {
+               float imageTheta = 0.0f;
+               DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
+               if (displayItem != null) {
+                       imageTheta = displayItem.getImageTheta();
+               }
+               mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, sDeltaAnchorPositionUncommited,
+                       mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
+       }
+
+       boolean constrainCameraForSlot(int slotIndex) {
+               return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, sDeltaAnchorPosition, mCurrentFocusItemWidth,
+                       mCurrentFocusItemHeight);
+       }
+
+       // Called on render thread before rendering.
+       @Override
+       public boolean update(RenderView view, float timeElapsed) {
+               if (mFeedAboutToChange == false) {
+                       mTimeElapsedSinceTransition += timeElapsed;
+                       mTimeElapsedSinceGridViewReady += timeElapsed;
+                       if (mTimeElapsedSinceGridViewReady >= 1.0f) {
+                               mTimeElapsedSinceGridViewReady = 1.0f;
+                       }
+                       mTimeElapsedSinceStackViewReady += timeElapsed;
+                       if (mTimeElapsedSinceStackViewReady >= 1.0f) {
+                               mTimeElapsedSinceStackViewReady = 1.0f;
+                       }
+               } else {
+                       mTimeElapsedSinceTransition = 0;
+               }
+               if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
+                       HudLayer hud = getHud();
+                       hud.getPathBar().setHidden(true);
+                       hud.getMenuBar().setHidden(true);
+                       if (hud.getMode() != HudLayer.MODE_NORMAL)
+                               hud.setMode(HudLayer.MODE_NORMAL);
+               }
+               if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
+                       sHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
+               } else {
+                       sHud.getPathBar().setAnimatedIcons(null);
+               }
+
+               // In that case, we need to commit the respective Display Items when the
+               // feed was updated.
+               GridCamera camera = mCamera;
+               camera.update(timeElapsed);
+               DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
+               if (anchorDisplayItem != null && !sHud.getTimeBar().isDragged()) {
+                       sHud.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);
+               } else {
+                       sHud.autoHide(false);
+                       sHud.setAlpha(1.0f);
+               }
+               GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
+               int numFullScreenQuads = fullscreenQuads.length;
+               for (int i = 0; i < numFullScreenQuads; ++i) {
+                       fullscreenQuads[i].update(timeElapsed);
+               }
+               if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
+                       mTimeElapsedSinceView += timeElapsed;
+                       if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
+                               // time to go to the next slide
+                               mTimeElapsedSinceView = 0.0f;
+                               changeFocusToNextSlot(0.5f);
+                               mCamera.commitMoveInX();
+                               mCamera.commitMoveInY();
+                       }
+               }
+               if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
+                       mCamera.moveYTo(-0.1f);
+                       mCamera.commitMoveInY();
+               }
+               boolean dirty = mDrawManager.update(timeElapsed);
+               dirty |= mSlideshowMode;
+               dirty |= mFramesDirty > 0;
+               ++mFrameCount;
+               if (mFramesDirty > 0) {
+                       --mFramesDirty;
+               }
+               try {
+                       if (mMediaFeed != null && (mMediaFeed.getWaitingForMediaScanner())) {
+                               // We limit the drawing of the frame so that the MediaScanner
+                               // thread can do its work
+                               Thread.sleep(200);
+                       }
+               } catch (InterruptedException e) {
+
+               }
+               if (sDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
+                       || (mTimeElapsedSinceTransition > 0.0f && mTimeElapsedSinceTransition < 1.0f) || mSelectedAlpha != mTargetAlpha
+                       // || (mAnimatedFov != mTargetFov)
+                       || dirty)
+                       return true;
+               else
+                       return false;
+       }
+
+       private void computeVisibleRange() {
+               if (mPerformingLayoutChange)
+                       return;
+               if (sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited) == false) {
+                       sDeltaAnchorPosition.set(sDeltaAnchorPositionUncommited);
+               }
+               mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, sDeltaAnchorPosition, sVisibleRange,
+                       sBufferedVisibleRange, sCompleteRange, mState);
+       }
+
+       private void computeVisibleItems() {
+               if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
+                       return;
+               }
+               computeVisibleRange();
+               int deltaBegin = sBufferedVisibleRange.begin - sPreviousDataRange.begin;
+               int deltaEnd = sBufferedVisibleRange.end - sPreviousDataRange.end;
+               if (deltaBegin != 0 || deltaEnd != 0) {
+                       // The delta has changed, we have to compute the display items
+                       // again.
+                       // We find the intersection range, these slots have not changed at
+                       // all.
+                       int firstVisibleSlotIndex = sBufferedVisibleRange.begin;
+                       int lastVisibleSlotIndex = sBufferedVisibleRange.end;
+                       sPreviousDataRange.begin = firstVisibleSlotIndex;
+                       sPreviousDataRange.end = lastVisibleSlotIndex;
+
+                       Pool<Vector3f> pool = sTempVec;
+                       Vector3f position = pool.create();
+                       Vector3f deltaAnchorPosition = pool.create();
+                       try {
+                               MediaFeed feed = mMediaFeed;
+                               DisplayList displayList = sDisplayList;
+                               DisplayItem[] displayItems = sDisplayItems;
+                               DisplaySlot[] displaySlots = sDisplaySlots;
+                               int numDisplayItems = displayItems.length;
+                               int numDisplaySlots = displaySlots.length;
+                               ArrayList<MediaItem> visibleItems = sVisibleItems;
+                               deltaAnchorPosition.set(sDeltaAnchorPosition);
+                               LayoutInterface layout = mLayoutInterface;
+                               GridCamera camera = mCamera;
+                               for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
+                                       GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
+                                       MediaSet set = feed.getSetForSlot(i);
+                                       int indexIntoSlots = i - firstVisibleSlotIndex;
+
+                                       if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
+                                               ArrayList<MediaItem> items = set.getItems();
+                                               displaySlots[indexIntoSlots].setMediaSet(set);
+                                               ArrayList<MediaItem> bestItems = sTempList;
+                                               if (mTimeElapsedSinceTransition < 1.0f) {
+                                                       // We always show the same top thumbnails for a
+                                                       // stack of albums
+                                                       if (mState == STATE_MEDIA_SETS)
+                                                               ArrayUtils.computeSortedIntersection(items, visibleItems, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
+                                                       else
+                                                               ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
+                                               }
+
+                                               int numItemsInSet = set.getNumItems();
+                                               int numBestItems = bestItems.size();
+                                               int originallyFoundItems = numBestItems;
+                                               if (numBestItems < MAX_ITEMS_PER_SLOT) {
+                                                       int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
+                                                       for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
+                                                               MediaItem item = items.get(currItemPos);
+                                                               if (mTimeElapsedSinceTransition >= 1.0f || !bestItems.contains(item)) {
+                                                                       bestItems.add(item);
+                                                                       if (--itemsRemaining == 0) {
+                                                                               break;
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                               numBestItems = bestItems.size();
+                                               int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
+                                               for (int j = 0; j < numBestItems; ++j) {
+                                                       if (baseIndex + j >= numDisplayItems) {
+                                                               break;
+                                                       }
+                                                       if (j >= numItemsInSet) {
+                                                               displayItems[baseIndex + j] = null;
+                                                       } else {
+                                                               MediaItem item = bestItems.get(j);
+                                                               if (item != null) {
+                                                                       DisplayItem displayItem = displayList.get(item);
+                                                                       if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
+                                                                               || (mState == STATE_GRID_VIEW && (mTimeElapsedSinceTransition > 1.0f || j >= originallyFoundItems))) {
+                                                                               displayItem.set(position, j, false);
+                                                                               displayItem.commit();
+                                                                       } else {
+                                                                               displayList.setPositionAndStackIndex(displayItem, position, j, true);
+                                                                       }
+                                                                       displayItems[baseIndex + j] = displayItem;
+                                                               }
+                                                       }
+                                               }
+                                               for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
+                                                       displayItems[baseIndex + j] = null;
+                                               }
+                                               bestItems.clear();
+                                       }
+                               }
+                               if (mFeedChanged) {
+                                       mFeedChanged = false;
+                                       if (mInputProcessor != null && mState == STATE_FULL_SCREEN && mRequestFocusContentUri == null) {
+                                               int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
+                                               if (currentSelectedSlot > sCompleteRange.end)
+                                                       currentSelectedSlot = sCompleteRange.end;
+                                               mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
+                                       }
+                                       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 (mRequestFocusContentUri != null) {
+                                               // We have to find the item that has this contentUri
+                                               if (mState == STATE_FULL_SCREEN) {
+                                                       int numSlots = sCompleteRange.end + 1;
+                                                       for (int i = 0; i < numSlots; ++i) {
+                                                               MediaSet set = feed.getSetForSlot(i);
+                                                               ArrayList<MediaItem> items = set.getItems();
+                                                               int numItems = items.size();
+                                                               for (int j = 0; j < numItems; ++j) {
+                                                                       String itemUri = items.get(j).mContentUri;
+                                                                       if (itemUri != null && mRequestFocusContentUri != null) {
+                                                                               if (itemUri.equals(mRequestFocusContentUri)) {
+                                                                                       mInputProcessor.setCurrentSelectedSlot(i);
+                                                                                       mRequestFocusContentUri = null;
+                                                                                       break;
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       } finally {
+                               pool.delete(position);
+                               pool.delete(deltaAnchorPosition);
+                       }
+                       // We keep upto 400 thumbnails in memory.
+                       int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
+                       int startMemoryRange = (sBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
+                       if (mStartMemoryRange != startMemoryRange) {
+                               mStartMemoryRange = startMemoryRange;
+                               clearUnusedThumbnails();
+                       }
+               }
+       }
+
+       @Override
+       public void handleLowMemory() {
+               clearUnusedThumbnails();
+               GridDrawables.sStringTextureTable.clear();
+               mBackground.clearCache();
+       }
+
+       // This method can be potentially expensive
+       public void clearUnusedThumbnails() {
+               sDisplayList.clearExcept(sDisplayItems);
+       }
+
+       @Override
+       public void onSurfaceCreated(RenderView view, GL11 gl) {
+               sDisplayList.clear();
+               sHud.clear();
+               sHud.reset();
+               GridDrawables.sStringTextureTable.clear();
+               mDrawables.onSurfaceCreated(view, gl);
+               mBackground.clear();
+       }
+
+       @Override
+       public void onSurfaceChanged(RenderView view, int width, int height) {
+               mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
+               view.setFov(mCamera.mFov);
+               setState(mState);
+       }
+
+       // Renders the node in a given pass.
+       public void renderOpaque(RenderView view, GL11 gl) {
+               GridCamera camera = mCamera;
+               int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
+               computeVisibleItems();
+
+               gl.glMatrixMode(GL11.GL_MODELVIEW);
+               gl.glLoadIdentity();
+               GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
+                       camera.mUpX, camera.mUpY, camera.mUpZ);
+               view.setAlpha(1.0f);
+               if (mSelectedAlpha != 1.0f) {
+                       gl.glEnable(GL11.GL_BLEND);
+                       gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+                       view.setAlpha(mSelectedAlpha);
+               }
+               if (selectedSlotIndex != Shared.INVALID) {
+                       mTargetAlpha = 0.0f;
+               } else {
+                       mTargetAlpha = 1.0f;
+               }
+               mDrawManager.prepareDraw(sBufferedVisibleRange, sVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
+                       mInputProcessor.isFocusItemPressed());
+               if (mSelectedAlpha != 0.0f) {
+                       mDrawManager.drawThumbnails(view, gl, mState);
+               }
+               if (mSelectedAlpha != 1.0f) {
+                       gl.glDisable(GL11.GL_BLEND);
+               }
+               // We draw the selected slotIndex.
+               if (selectedSlotIndex != Shared.INVALID) {
+                       mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
+                       mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
+                       mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
+               }
+               view.setAlpha(mSelectedAlpha);
+       }
+
+       public void renderBlended(RenderView view, GL11 gl) {
+               // We draw the placeholder for all visible slots.
+               if (sHud != null && mDrawManager != null) {
+                       mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, sHud.getMode(), mTimeElapsedSinceStackViewReady,
+                               mTimeElapsedSinceGridViewReady, sBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
+                                       || mMediaFeed.isLoading());
+               }
+       }
+
+       public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
+               if (mPerformingLayoutChange || !sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited)) {
+                       return;
+               }
+
+               mTimeElapsedSinceTransition = 0.0f;
+               mPerformingLayoutChange = true;
+               LayoutInterface layout = mLayoutInterface;
+               if (oldLayout == null) {
+                       oldLayout = sfullScreenLayoutInterface;
+               }
+               GridCamera camera = mCamera;
+               if (currentAnchorSlotIndex == Shared.INVALID) {
+                       currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
+                       if (mCurrentExpandedSlot != Shared.INVALID) {
+                               currentAnchorSlotIndex = mCurrentExpandedSlot;
+                       }
+                       int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
+                       if (selectedSlotIndex != Shared.INVALID) {
+                               currentAnchorSlotIndex = selectedSlotIndex;
+                       }
+               }
+               if (newAnchorSlotIndex == Shared.INVALID) {
+                       newAnchorSlotIndex = currentAnchorSlotIndex;
+               }
+               int itemHeight = camera.mItemHeight;
+               int itemWidth = camera.mItemWidth;
+               Pool<Vector3f> pool = sTempVec;
+               Vector3f deltaAnchorPosition = pool.create();
+               Vector3f currentSlotPosition = pool.create();
+               try {
+                       deltaAnchorPosition.set(0, 0, 0);
+                       if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
+                               layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
+                               oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
+                               currentSlotPosition.subtract(sDeltaAnchorPosition);
+                               deltaAnchorPosition.subtract(currentSlotPosition);
+                               deltaAnchorPosition.y = 0;
+                               deltaAnchorPosition.z = 0;
+                       }
+                       sDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
+               } finally {
+                       pool.delete(deltaAnchorPosition);
+                       pool.delete(currentSlotPosition);
+               }
+               centerCameraForSlot(newAnchorSlotIndex, 1.0f);
+               mCurrentExpandedSlot = Shared.INVALID;
+
+               // Force recompute of visible items and their positions.
+               ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
+               ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
+               ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
+               forceRecomputeVisibleRange();
+               mPerformingLayoutChange = false;
+       }
+
+       private void forceRecomputeVisibleRange() {
+               sPreviousDataRange.begin = Shared.INVALID;
+               sPreviousDataRange.end = Shared.INVALID;
+               if (mView != null) {
+                       mView.requestRender();
+               }
+       }
+
+       // called on background thread
+       public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
+               if (!needsLayout) {
+                       mFeedChanged = true;
+                       forceRecomputeVisibleRange();
+                       if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
+                               sHud.setFeed(feed, mState, needsLayout);
+                       return;
+               }
+
+               while (mPerformingLayoutChange == true) {
+                       Thread.yield();
+               }
+               if (mState == STATE_GRID_VIEW) {
+                       if (sHud != null) {
+                               MediaSet set = feed.getCurrentSet();
+                               if (set != null && !mLocationFilter)
+                                       sHud.getPathBar().changeLabel(set.mNoCountTitleString);
+                       }
+               }
+               DisplayItem[] displayItems = sDisplayItems;
+               int firstBufferedVisibleSlotIndex = sBufferedVisibleRange.begin;
+               int lastBufferedVisibleSlotIndex = sBufferedVisibleRange.end;
+               int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
+               if (mCurrentExpandedSlot != Shared.INVALID) {
+                       currentlyVisibleSlotIndex = mCurrentExpandedSlot;
+               }
+               MediaItem anchorItem = null;
+               ArrayList<MediaItem> visibleItems = sVisibleItems;
+               visibleItems.clear();
+               visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
+               if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
+                       && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
+                       int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
+                       for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
+                               DisplayItem displayItem = displayItems[baseIndex + i];
+                               if (displayItem != null) {
+                                       if (anchorItem == null) {
+                                               anchorItem = displayItem.mItemRef;
+                                       }
+                                       visibleItems.add(displayItem.mItemRef);
+                               }
+                       }
+               }
+               // We want to add items from the middle.
+               int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
+               int midPoint = (lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex) / 2;
+               int length = displayItems.length;
+               for (int i = 0; i < numItems; ++i) {
+                       int index = midPoint + Shared.midPointIterator(i);
+                       int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
+                       if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
+                               for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
+                                       DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
+                                       if (displayItem != null) {
+                                               MediaItem item = displayItem.mItemRef;
+                                               if (item != anchorItem) {
+                                                       visibleItems.add(item);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               int newSlotIndex = Shared.INVALID;
+               if (anchorItem != null) {
+                       // We try to find the anchor item in the new feed.
+                       int numSlots = feed.getNumSlots();
+                       for (int i = 0; i < numSlots; ++i) {
+                               MediaSet set = feed.getSetForSlot(i);
+                               if (set != null && set.containsItem(anchorItem)) {
+                                       newSlotIndex = i;
+                                       break;
+                               }
+                       }
+               }
+
+               if (anchorItem != null && newSlotIndex == Shared.INVALID) {
+                       int numSlots = feed.getNumSlots();
+                       MediaSet parentSet = anchorItem.mParentMediaSet;
+                       for (int i = 0; i < numSlots; ++i) {
+                               MediaSet set = feed.getSetForSlot(i);
+                               if (set != null && set.mId == parentSet.mId) {
+                                       newSlotIndex = i;
+                                       break;
+                               }
+                       }
+               }
+
+               // We must create a new display store now since the data has changed.
+               if (newSlotIndex != Shared.INVALID) {
+                       if (mState == STATE_MEDIA_SETS) {
+                               sDisplayList.clearExcept(displayItems);
+                       }
+                       onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
+               } else {
+                       forceRecomputeVisibleRange();
+               }
+               mCurrentExpandedSlot = Shared.INVALID;
+               mFeedAboutToChange = false;
+               mFeedChanged = true;
+               if (feed != null) {
+                       if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
+                               sHud.setFeed(feed, mState, needsLayout);
+               }
+               if (mView != null) {
+                       mView.requestRender();
+               }
+       }
+
+       public DisplayItem getRepresentativeDisplayItem() {
+               int slotIndex = Shared.INVALID;
+               if (mInputProcessor != null) {
+                       slotIndex = mInputProcessor.getCurrentFocusSlot();
+               }
+               if (slotIndex == Shared.INVALID) {
+                       slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
+               }
+               int index = (slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
+               if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
+                       return sDisplayItems[index];
+               } else {
+                       return null;
+               }
+       }
+
+       public DisplayItem getAnchorDisplayItem(int type) {
+               int slotIndex = getAnchorSlotIndex(type);
+               return sDisplayItems[(slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT];
+       }
+
+       public float getScrollPosition() {
+               return (mCamera.mLookAtX * mCamera.mScale + sDeltaAnchorPosition.x); // in
+               // pixels
+       }
+
+       public DisplayItem getDisplayItemForScrollPosition(float posX) {
+               Pool<Vector3f> pool = sTempVecAlt;
+               MediaFeed feed = mMediaFeed;
+               int itemWidth = mCamera.mItemWidth;
+               int itemHeight = mCamera.mItemHeight;
+               GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
+               float absolutePosX = posX;
+               int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
+               int right = feed == null ? 0 : (int) (feed.getNumSlots());
+               int retSlot = left;
+               Vector3f position = pool.create();
+               try {
+                       for (int i = left; i < right; ++i) {
+                               gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
+                               retSlot = i;
+                               if (position.x >= absolutePosX) {
+                                       break;
+                               }
+                       }
+               } finally {
+                       pool.delete(position);
+               }
+               if (mFeedAboutToChange) {
+                       return null;
+               }
+               right = feed == null ? 0 : feed.getNumSlots();
+               if (right == 0) {
+                       return null;
+               }
+
+               if (retSlot >= right)
+                       retSlot = right - 1;
+               MediaSet set = feed.getSetForSlot(retSlot);
+               if (set != null) {
+                       ArrayList<MediaItem> items = set.getItems();
+                       if (items != null && set.getNumItems() > 0) {
+                               return (sDisplayList.get(items.get(0)));
+                       }
+               }
+               return null;
+       }
+
+       // Returns the top left-most item.
+       public int getAnchorSlotIndex(int anchorType) {
+               int retVal = 0;
+               switch (anchorType) {
+               case ANCHOR_LEFT:
+                       retVal = sVisibleRange.begin;
+                       break;
+               case ANCHOR_RIGHT:
+                       retVal = sVisibleRange.end;
+                       break;
+               case ANCHOR_CENTER:
+                       retVal = (sVisibleRange.begin + sVisibleRange.end) / 2;
+                       break;
+               }
+               return retVal;
+       }
+
+       DisplayItem getDisplayItemForSlotId(int slotId) {
+               int index = slotId - sBufferedVisibleRange.begin;
+               if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
+                       return sDisplayItems[index * MAX_ITEMS_PER_SLOT];
+               }
+               return null;
+       }
+
+       boolean changeFocusToNextSlot(float convergence) {
+               int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
+               boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
+               if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
+                       endSlideshow();
+                       sHud.setAlpha(1.0f);
+               }
+               return retVal;
+       }
+
+       boolean changeFocusToSlot(int slotId, float convergence) {
+               mZoomValue = 1.0f;
+               int index = slotId - sBufferedVisibleRange.begin;
+               if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
+                       DisplayItem displayItem = sDisplayItems[index * MAX_ITEMS_PER_SLOT];
+                       if (displayItem != null) {
+                               MediaItem item = displayItem.mItemRef;
+                               sHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
+                               if (slotId != Shared.INVALID && slotId <= sCompleteRange.end) {
+                                       mInputProcessor.setCurrentFocusSlot(slotId);
+                                       centerCameraForSlot(slotId, convergence);
+                                       return true;
+                               } else {
+                                       centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
+                                       return false;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       boolean changeFocusToPreviousSlot(float convergence) {
+               return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
+       }
+
+       public ArrayList<MediaBucket> getSelectedBuckets() {
+               return sBucketList.get();
+       }
+
+       public void selectAll() {
+               if (mState != STATE_FULL_SCREEN) {
+                       int numSlots = sCompleteRange.end + 1;
+                       for (int i = 0; i < numSlots; ++i) {
+                               addSlotToSelectedItems(i, false, false);
+                       }
+                       updateCountOfSelectedItems();
+               } else {
+                       addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
+               }
+       }
+
+       public void deselectOrCancelSelectMode() {
+               if (sBucketList.size() == 0) {
+                       sHud.cancelSelection();
+               } else {
+                       sBucketList.clear();
+                       updateCountOfSelectedItems();
+               }
+       }
+
+       public void deselectAll() {
+               sHud.cancelSelection();
+               sBucketList.clear();
+               updateCountOfSelectedItems();
+       }
+
+       public void deleteSelection() {
+               // Delete the selection and exit selection mode.
+               mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
+               deselectAll();
+
+               // If the current set is now empty, return to the parent set.
+               if (sCompleteRange.isEmpty()) {
+                       goBack(); // TODO(venkat): This does not work most of the time, can
+                                         // you take a look?
+               }
+       }
+
+       void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
+               if (mFeedAboutToChange == false) {
+                       MediaFeed feed = mMediaFeed;
+                       sBucketList.add(slotId, feed, removeIfAlreadyAdded);
+                       if (updateCount) {
+                               updateCountOfSelectedItems();
+                               if (sBucketList.size() == 0)
+                                       deselectAll();
+                       }
+               }
+               sHud.computeBottomMenu();
+       }
+
+       private void updateCountOfSelectedItems() {
+               sHud.updateNumItemsSelected(sBucketList.size());
+       }
+
+       public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
+               return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * Gallery.PIXEL_DENSITY), mCamera.mItemHeight
+                       + (int) (100 * Gallery.PIXEL_DENSITY));
+       }
+
+       public int getSlotIndexForScreenPosition(int posX, int posY) {
+               return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
+       }
+
+       private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
+               Pool<Vector3f> pool = sTempVec;
+               int retVal = 0;
+               Vector3f worldPos = pool.create();
+               try {
+                       GridCamera camera = mCamera;
+                       camera.convertToCameraSpace(posX, posY, 0, worldPos);
+                       // slots are expressed in pixels as well
+                       worldPos.x *= camera.mScale;
+                       worldPos.y *= camera.mScale;
+                       // we ignore z
+                       retVal = hitTest(worldPos, itemWidth, itemHeight);
+               } finally {
+                       pool.delete(worldPos);
+               }
+               return retVal;
+       }
+
+       public boolean tapGesture(int slotIndex, boolean metadata) {
+               MediaFeed feed = mMediaFeed;
+               if (!feed.isClustered()) {
+                       // It is not clustering.
+                       if (!feed.hasExpandedMediaSet()) {
+                               if (feed.canExpandSet(slotIndex)) {
+                                       mCurrentExpandedSlot = slotIndex;
+                                       feed.expandMediaSet(slotIndex);
+                                       setState(STATE_GRID_VIEW);
+                               }
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       // Select a cluster, and recompute a new cluster within this
+                       // cluster.
+                       mCurrentExpandedSlot = slotIndex;
+                       goBack();
+                       if (metadata) {
+                               DisplaySlot slot = sDisplaySlots[slotIndex - sBufferedVisibleRange.begin];
+                               if (slot.hasValidLocation()) {
+                                       MediaSet set = slot.getMediaSet();
+                                       if (set.mReverseGeocodedLocation != null) {
+                                               enableLocationFiltering(set.mReverseGeocodedLocation);
+                                       }
+                                       feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude,
+                                               set.mMaxLonLongitude));
+                               }
+                       }
+                       return false;
+               }
+       }
+
+       public void onTimeChanged(TimeBar timebar) {
+               if (mFeedAboutToChange) {
+                       return;
+               }
+               // TODO lot of optimization possible here
+               MediaItem item = timebar.getItem();
+               MediaFeed feed = mMediaFeed;
+               int numSlots = feed.getNumSlots();
+               for (int i = 0; i < numSlots; ++i) {
+                       MediaSet set = feed.getSetForSlot(i);
+                       if (set == null) {
+                               return;
+                       }
+                       ArrayList<MediaItem> items = set.getItems();
+                       if (items == null || set.getNumItems() == 0) {
+                               return;
+                       }
+                       if (items.contains(item)) {
+                               centerCameraForSlot(i, 1.0f);
+                               break;
+                       }
+               }
+       }
+
+       public void onFeedAboutToChange(MediaFeed feed) {
+               mFeedAboutToChange = true;
+               mTimeElapsedSinceTransition = 0;
+       }
+
+       public void startSlideshow() {
+               endSlideshow();
+               mSlideshowMode = true;
+               mZoomValue = 1.0f;
+               centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
+               mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
+               sHud.setAlpha(0);
+               PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+               mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
+               mWakeLock.acquire();
+       }
+
+       public void enterSelectionMode() {
+               mSlideshowMode = false;
+               sHud.enterSelectionMode();
+               int currentSlot = mInputProcessor.getCurrentSelectedSlot();
+               if (currentSlot == Shared.INVALID) {
+                       currentSlot = mInputProcessor.getCurrentFocusSlot();
+               }
+               addSlotToSelectedItems(currentSlot, false, true);
+       }
+
+       private float getFillScreenZoomValue() {
+               return GridCameraManager.getFillScreenZoomValue(mCamera, sTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
+       }
+
+       public void zoomInToSelectedItem() {
+               mSlideshowMode = false;
+               float potentialZoomValue = getFillScreenZoomValue();
+               if (mZoomValue < potentialZoomValue) {
+                       mZoomValue = potentialZoomValue;
+               } else {
+                       mZoomValue *= 3.0f;
+               }
+               if (mZoomValue > 6.0f) {
+                       mZoomValue = 6.0f;
+               }
+               sHud.setAlpha(1.0f);
+               centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
+       }
+
+       public void zoomOutFromSelectedItem() {
+               mSlideshowMode = false;
+               if (mZoomValue == getFillScreenZoomValue()) {
+                       mZoomValue = 1.0f;
+               } else {
+                       mZoomValue /= 3.0f;
+               }
+               if (mZoomValue < 1.0f) {
+                       mZoomValue = 1.0f;
+               }
+               sHud.setAlpha(1.0f);
+               centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
+       }
+
+       public void rotateSelectedItems(float f) {
+               MediaBucketList bucketList = sBucketList;
+               ArrayList<MediaBucket> mediaBuckets = bucketList.get();
+               DisplayList displayList = sDisplayList;
+               int numBuckets = mediaBuckets.size();
+               for (int i = 0; i < numBuckets; ++i) {
+                       MediaBucket bucket = mediaBuckets.get(i);
+                       ArrayList<MediaItem> mediaItems = bucket.mediaItems;
+                       if (mediaItems != null) {
+                               int numMediaItems = mediaItems.size();
+                               for (int j = 0; j < numMediaItems; ++j) {
+                                       MediaItem item = mediaItems.get(j);
+                                       DisplayItem displayItem = displayList.get(item);
+                                       displayItem.rotateImageBy(f);
+                                       displayList.addToAnimatables(displayItem);
+                               }
+                       }
+               }
+               if (mState == STATE_FULL_SCREEN) {
+                       centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
+               }
+               mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
+               // we recreate these displayitems from the cache
+       }
+
+       public void cropSelectedItem() {
+
+       }
+
+       @Override
+       public boolean onTouchEvent(MotionEvent event) {
+               return mInputProcessor.onTouchEvent(event);
+       }
+
+       @Override
+       public boolean onKeyDown(int keyCode, KeyEvent event) {
+               if (mInputProcessor != null)
+                       return mInputProcessor.onKeyDown(keyCode, event, mState);
+               return false;
+       }
+
+       public boolean inSlideShowMode() {
+               return mSlideshowMode;
+       }
+
+       public boolean noDeleteMode() {
+               return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
+       }
+
+       public float getZoomValue() {
+               return mZoomValue;
+       }
+
+       public boolean feedAboutToChange() {
+               return mFeedAboutToChange;
+       }
+
+       public boolean isInAlbumMode() {
+               return mInAlbum;
+       }
+
+       public Vector3f getDeltaAnchorPosition() {
+               return sDeltaAnchorPosition;
+       }
+
+       public int getExpandedSlot() {
+               return mCurrentExpandedSlot;
+       }
+
+       public GridLayoutInterface getLayoutInterface() {
+               return (GridLayoutInterface) mLayoutInterface;
+       }
+
+       public void setZoomValue(float f) {
+               mZoomValue = f;
+               centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
+       }
+
+       public void setPickIntent(boolean b) {
+               mPickIntent = b;
+               sHud.getPathBar().popLabel();
+               sHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
+                       new Runnable() {
+                               public void run() {
+                                       if (sHud.getAlpha() == 1.0f) {
+                                               if (!mFeedAboutToChange) {
+                                                       setState(STATE_MEDIA_SETS);
+                                               }
+                                       } else {
+                                               sHud.setAlpha(1.0f);
+                                       }
+                               }
+                       });
+       }
+
+       public boolean getPickIntent() {
+               return mPickIntent;
+       }
+
+       public void setViewIntent(boolean b, final String setName) {
+               mViewIntent = b;
+               if (b) {
+                       mMediaFeed.expandMediaSet(0);
+                       setState(STATE_GRID_VIEW);
+                       // We need to make sure we haven't pushed the same label twice
+                       if (sHud.getPathBar().getNumLevels() == 1) {
+                               sHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
+                                       public void run() {
+                                               if (mFeedAboutToChange) {
+                                                       return;
+                                               }
+                                               if (sHud.getAlpha() == 1.0f) {
+                                                       disableLocationFiltering();
+                                                       if (mInputProcessor != null)
+                                                               mInputProcessor.clearSelection();
+                                                       setState(STATE_GRID_VIEW);
+                                               } else {
+                                                       sHud.setAlpha(1.0f);
+                                               }
+                                       }
+                               });
+                       }
+               }
+       }
+
+       public boolean getViewIntent() {
+               return mViewIntent;
+       }
+
+       public void setSingleImage(boolean noDeleteMode) {
+               mNoDeleteMode = noDeleteMode;
+               mInputProcessor.setCurrentSelectedSlot(0);
+       }
+
+       public MediaFeed getFeed() {
+               return mMediaFeed;
+       }
+
+       public void markDirty(int numFrames) {
+               mFramesDirty = numFrames;
+       }
+
+       public void focusItem(String contentUri) {
+               mRequestFocusContentUri = contentUri;
+               mMediaFeed.updateListener(false);
+       }
 
 }
index e2d1bef..d7db544 100644 (file)
@@ -396,6 +396,7 @@ public final class HudLayer extends Layer {
 
     void setGridLayer(GridLayer layer) {
         mGridLayer = layer;
+        updateViews();
     }
 
     int getMode() {
@@ -754,7 +755,6 @@ public final class HudLayer extends Layer {
     public void enterSelectionMode() {
         setAlpha(1.0f);
         setMode(HudLayer.MODE_SELECT);
-
         // if we are in single view mode, show the bottom menu without the delete button.
         if (mGridLayer.noDeleteMode()) {
             mSelectionMenuBottom.setMenus(mSingleViewIntentBottomMenu);
@@ -762,6 +762,22 @@ public final class HudLayer extends Layer {
             mSelectionMenuBottom.setMenus(mNormalBottomMenu);
         }
     }
+    
+    public void computeBottomMenu() {
+       // we need to the same for picasa albums
+        ArrayList<MediaBucket> selection = mGridLayer.getSelectedBuckets();
+        Menu[] menus = mSelectionMenuBottom.getMenus();
+        if (menus == mSingleViewIntentBottomMenu)
+               return;
+        int numBuckets = selection.size();
+        for (int i = 0; i < numBuckets; ++i) {
+               MediaBucket bucket = selection.get(i);
+               if (bucket.mediaSet.mPicasaAlbumId != Shared.INVALID) {
+                       mSelectionMenuBottom.setMenus(mSingleViewIntentBottomMenu);
+                       break;
+               }
+        }
+    }
 
     public Layer getMenuBar() {
         return mFullscreenMenu;
index acd8d70..e6b89ac 100644 (file)
@@ -32,6 +32,8 @@ public final class LocalDataSource implements DataSource {
     public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING;
     public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
     public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME);
+
+       public static boolean sObserverActive = false;
     private boolean mDisableImages;
     private boolean mDisableVideos;
 
@@ -43,8 +45,7 @@ public final class LocalDataSource implements DataSource {
     }
 
     private Context mContext;
-    private ContentObserver mImagesObserver;
-    private ContentObserver mVideosObserver;
+       private ContentObserver mObserver;
 
     public LocalDataSource(Context context) {
         mContext = context;
@@ -62,35 +63,29 @@ public final class LocalDataSource implements DataSource {
         stopListeners();
         CacheService.loadMediaSets(feed, this, !mDisableImages, !mDisableVideos);
         Handler handler = ((Gallery) mContext).getHandler();
-        ContentObserver imagesObserver = new ContentObserver(handler) {
-            public void onChange(boolean selfChange) {
-                if (((Gallery) mContext).isPaused()) {
-                    refresh(feed, CAMERA_BUCKET_ID);
-                    refresh(feed, DOWNLOAD_BUCKET_ID);
-
-                    MediaSet set = feed.getCurrentSet();
-                    if (set != null && set.mPicasaAlbumId == Shared.INVALID) {
-                        refresh(feed, set.mId);
-                    }
-                }
-            }
-        };
-        ContentObserver videosObserver = new ContentObserver(handler) {
+        ContentObserver observer = new ContentObserver(handler) {
             public void onChange(boolean selfChange) {
-                if (((Gallery) mContext).isPaused()) {
-                    refresh(feed, CAMERA_BUCKET_ID);
-                }
+               CacheService.senseDirty(mContext, new CacheService.Observer() {
+                       public void onChange(long[] ids) {
+                               if (ids != null) {
+                                       int numLongs = ids.length;
+                                       for (int i = 0; i < numLongs; ++i) {
+                                               refreshUI(feed, ids[i]);
+                                       }
+                               }
+                       }
+               });
             }
         };
-
-        // Start listening. TODO: coalesce update notifications while mediascanner is active.
+        
+        // Start listening.
         Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
         Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
         ContentResolver cr = mContext.getContentResolver();
-        mImagesObserver = imagesObserver;
-        mVideosObserver = videosObserver;
-        cr.registerContentObserver(uriImages, false, mImagesObserver);
-        cr.registerContentObserver(uriVideos, false, mVideosObserver);
+        mObserver = observer;
+        cr.registerContentObserver(uriImages, false, observer);
+        cr.registerContentObserver(uriVideos, false, observer);
+        sObserverActive = true;
     }
 
     public void shutdown() {
@@ -101,23 +96,18 @@ public final class LocalDataSource implements DataSource {
     
     private void stopListeners() {
         ContentResolver cr = mContext.getContentResolver();
-        if (mImagesObserver != null) {
-            cr.unregisterContentObserver(mImagesObserver);
-        }
-        if (mVideosObserver != null) {
-            cr.unregisterContentObserver(mVideosObserver);
+        if (mObserver != null) {
+            cr.unregisterContentObserver(mObserver);
         }
+        sObserverActive = false;
     }
-
-    protected void refresh(MediaFeed feed, long setIdToUse) {
+    
+    protected void refreshUI(MediaFeed feed, long setIdToUse) {
         if (setIdToUse == Shared.INVALID) {
             return;
         }
         Log.i(TAG, "Refreshing local data source");
-        Gallery.NEEDS_REFRESH = true;
         if (feed.getMediaSet(setIdToUse) == null) {
-            if (!CacheService.setHasItems(mContext.getContentResolver(), setIdToUse))
-                return;
             MediaSet mediaSet = feed.addMediaSet(setIdToUse, this);
             if (setIdToUse == CAMERA_BUCKET_ID) {
                 mediaSet.mName = CAMERA_STRING;
@@ -125,8 +115,6 @@ public final class LocalDataSource implements DataSource {
                 mediaSet.mName = DOWNLOAD_STRING;
             }
             mediaSet.generateTitle(true);
-            if (!CacheService.isPresentInCache(setIdToUse))
-                CacheService.markDirty(mContext);
         } else {
             MediaSet mediaSet = feed.replaceMediaSet(setIdToUse, this);
             if (setIdToUse == CAMERA_BUCKET_ID) {
@@ -135,7 +123,6 @@ public final class LocalDataSource implements DataSource {
                 mediaSet.mName = DOWNLOAD_STRING;
             }
             mediaSet.generateTitle(true);
-            CacheService.markDirty(mContext, setIdToUse);
         }
     }
 
@@ -152,7 +139,7 @@ public final class LocalDataSource implements DataSource {
             return;
         }
         CacheService.loadMediaItemsIntoMediaFeed(mediaFeed, set, rangeStart, rangeEnd, !mDisableImages, !mDisableVideos);
-        if (set.mId == CAMERA_BUCKET_ID && set.mNumItemsLoaded > 0) {
+        if (set.mId == CAMERA_BUCKET_ID) {
             mediaFeed.moveSetToFront(set);
         }
     }
index 3e0f568..773fefc 100644 (file)
@@ -3,6 +3,7 @@ package com.cooliris.media;
 import java.util.ArrayList;
 import java.util.HashMap;
 
+
 public final class MediaBucketList {
     private static final Boolean TRUE = new Boolean(true);
     private static final Boolean FALSE = new Boolean(false);
index 0bd34ae..e3b9f87 100644 (file)
@@ -30,14 +30,31 @@ public final class PicasaDataSource implements DataSource {
     private ContentProviderClient mProviderClient;
     private final Context mContext;
     private ContentObserver mAlbumObserver;
-    private HashMap<String, Boolean> mAccountEnabled = new HashMap<String, Boolean>();
 
-    public PicasaDataSource(Context context) {
+    public PicasaDataSource(final Context context) {
         mContext = context;
     }
+    
+    public static final HashMap<String, Boolean> getAccountStatus(final Context context) {
+       final Account[] accounts = PicasaApi.getAccounts(context);
+        int numAccounts = accounts.length;
+        HashMap<String, Boolean> accountsEnabled = new HashMap<String, Boolean>(numAccounts);
+        for (int i = 0; i < numAccounts; ++i) {
+            Account account = accounts[i];
+            boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
+            String username = account.name;
+            if (username.contains("@gmail.") || username.contains("@googlemail.")) {
+                // Strip the domain from GMail accounts for canonicalization. TODO: is there an official way?
+                username = username.substring(0, username.indexOf('@'));
+            }
+            accountsEnabled.put(username, new Boolean(isEnabled));
+        }
+        return accountsEnabled;
+    }
 
     public void loadMediaSets(final MediaFeed feed) {
-        if (mProviderClient == null) {
+        // We do this here and not in the constructor to speed application loading time since this method is called in a background thread
+       if (mProviderClient == null) {
             mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
         }
         // Force permission dialog to be displayed if necessary. TODO: remove this after signed by Google.
@@ -45,8 +62,8 @@ public final class PicasaDataSource implements DataSource {
 
         // Ensure that users are up to date. TODO: also listen for accounts changed broadcast.
         PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0);
-        Handler handler = ((Gallery) mContext).getHandler();
-        ContentObserver albumObserver = new ContentObserver(handler) {
+        final Handler handler = ((Gallery) mContext).getHandler();
+        final ContentObserver albumObserver = new ContentObserver(handler) {
             public void onChange(boolean selfChange) {
                 loadMediaSetsIntoFeed(feed, true);
             }
@@ -77,34 +94,23 @@ public final class PicasaDataSource implements DataSource {
     }
 
     protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) {
-        Account[] accounts = PicasaApi.getAccounts(mContext);
-        int numAccounts = accounts.length;
-        for (int i = 0; i < numAccounts; ++i) {
-            Account account = accounts[i];
-            boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
-            String username = account.name;
-            if (username.contains("@gmail.") || username.contains("@googlemail.")) {
-                // Strip the domain from GMail accounts for canonicalization. TODO: is there an official way?
-                username = username.substring(0, username.indexOf('@'));
-            }
-            mAccountEnabled.put(username, new Boolean(isEnabled));
-        }
-        ContentProviderClient client = mProviderClient;
+        final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext);
+        final ContentProviderClient client = mProviderClient;
         if (client == null)
             return;
         try {
-            EntrySchema albumSchema = AlbumEntry.SCHEMA;
-            Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
+            final EntrySchema albumSchema = AlbumEntry.SCHEMA;
+            final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
                     DEFAULT_BUCKET_SORT_ORDER);
-            AlbumEntry album = new AlbumEntry();
+            final AlbumEntry album = new AlbumEntry();
             MediaSet mediaSet;
             if (cursor.moveToFirst()) {
-                int numAlbums = cursor.getCount();
-                ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
+                final int numAlbums = cursor.getCount();
+                final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
                 do {
                     albumSchema.cursorToObject(cursor, album);
-                    Boolean accountEnabledObj = mAccountEnabled.get(album.user);
-                    boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
+                    final Boolean accountEnabledObj = accountsEnabled.get(album.user);
+                    final boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
                     if (accountEnabled) {
                         mediaSet = feed.getMediaSet(album.id);
                         if (mediaSet == null) {
@@ -128,14 +134,14 @@ public final class PicasaDataSource implements DataSource {
     }
 
     private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) {
-        ContentProviderClient client = mProviderClient;
+        final ContentProviderClient client = mProviderClient;
         Cursor cursor = null;
         try {
             // Query photos in the album.
-            EntrySchema photosSchema = PhotoProjection.SCHEMA;
-            String whereInAlbum = "album_id = " + Long.toString(set.mId);
+            final EntrySchema photosSchema = PhotoProjection.SCHEMA;
+            final String whereInAlbum = "album_id = " + Long.toString(set.mId);
             cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null);
-            PhotoProjection photo = new PhotoProjection();
+            final PhotoProjection photo = new PhotoProjection();
             int count = cursor.getCount();
             if (count < end) {
                 end = count;
@@ -143,7 +149,7 @@ public final class PicasaDataSource implements DataSource {
             set.setNumExpectedItems(count);
             set.generateTitle(true);
             // Move to the next unread item.
-            int newIndex = start + 1;
+            final int newIndex = start + 1;
             if (newIndex > count || !cursor.move(newIndex)) {
                 end = 0;
                 cursor.close();
@@ -161,7 +167,7 @@ public final class PicasaDataSource implements DataSource {
             }
             for (int i = 0; i < end; ++i) {
                 photosSchema.cursorToObject(cursor, photo);
-                MediaItem item = new MediaItem();
+                final MediaItem item = new MediaItem();
                 item.mId = photo.id;
                 item.mEditUri = photo.editUri;
                 item.mMimeType = photo.contentType;
@@ -189,9 +195,6 @@ public final class PicasaDataSource implements DataSource {
         }
     }
 
-    public void prime(final MediaItem item) {
-    }
-
     public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
         try {
             if (operation == MediaFeed.OPERATION_DELETE) {
index 1fc4238..1b0184c 100644 (file)
@@ -18,6 +18,7 @@ import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.view.MotionEvent;
+
 import com.cooliris.media.RenderView.Lists;
 
 public final class TimeBar extends Layer implements MediaFeed.Listener {