• R/O
  • HTTP
  • SSH
  • HTTPS

提交

标签
No Tags

Frequently used words (click to add to your profile)

javaandroidc++linuxc#windowsobjective-ccocoaqt誰得pythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

packages/apps/Gallery2


Commit MetaInfo

修订版dc901a257e4487f3e45ed49316dffb1784f29815 (tree)
时间2009-12-04 11:52:47
作者Dave Sparks <davidsparks@andr...>
CommiterDave Sparks

Log Message

Updates to 3D gallery. Build 1203.

更改概述

差异

Binary files a/src.zip and /dev/null differ
--- a/src/com/cooliris/cache/BootReceiver.java
+++ b/src/com/cooliris/cache/BootReceiver.java
@@ -1,19 +1,25 @@
11 package com.cooliris.cache;
22
3-import com.cooliris.media.Gallery;
3+import com.cooliris.media.LocalDataSource;
44 import com.cooliris.media.SingleDataSource;
55
66 import android.content.BroadcastReceiver;
7+import android.content.ContentResolver;
78 import android.content.Context;
89 import android.content.Intent;
10+import android.database.ContentObserver;
911 import android.net.Uri;
12+import android.os.Handler;
13+import android.provider.MediaStore.Images;
14+import android.provider.MediaStore.Video;
1015 import android.util.Log;
1116
1217 public class BootReceiver extends BroadcastReceiver {
13- private static final String TAG = "BootReceiver";
18+ private static final String TAG = "BootReceiver";
19+ private final Handler mHandler = new Handler();
1420
1521 @Override
16- public void onReceive(Context context, Intent intent) {
22+ public void onReceive(final Context context, Intent intent) {
1723 final String action = intent.getAction();
1824 Log.i(TAG, "Got intent with action " + action);
1925 if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
@@ -29,7 +35,21 @@ public class BootReceiver extends BroadcastReceiver {
2935 CacheService.markDirty(context);
3036 }
3137 } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
32- Gallery.NEEDS_REFRESH = true;
38+ // We add special listeners for the MediaProvider
39+ final Handler handler = mHandler;
40+ final ContentObserver localObserver = new ContentObserver(handler) {
41+ public void onChange(boolean selfChange) {
42+ if (!LocalDataSource.sObserverActive) {
43+ CacheService.senseDirty(context, null);
44+ }
45+ }
46+ };
47+ // Start listening perpetually.
48+ Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
49+ Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
50+ ContentResolver cr = context.getContentResolver();
51+ cr.registerContentObserver(uriImages, false, localObserver);
52+ cr.registerContentObserver(uriVideos, false, localObserver);
3353 }
3454 }
3555 }
--- a/src/com/cooliris/cache/CacheService.java
+++ b/src/com/cooliris/cache/CacheService.java
@@ -24,6 +24,7 @@ import android.content.ContentValues;
2424 import android.content.Context;
2525 import android.content.Intent;
2626 import android.database.Cursor;
27+import android.database.MergeCursor;
2728 import android.graphics.Bitmap;
2829 import android.graphics.Canvas;
2930 import android.graphics.Paint;
@@ -53,1026 +54,1153 @@ import com.cooliris.media.UriTexture;
5354 import com.cooliris.media.Utils;
5455
5556 public final class CacheService extends IntentService {
56- public static final String ACTION_CACHE = "com.cooliris.cache.action.CACHE";
57- public static final DiskCache sAlbumCache = new DiskCache("local-album-cache");
58-
59- private static final String TAG = "CacheService";
60- private static ImageList sList = null;
61-
62- // Wait 2 seconds to start the thumbnailer so that the application can load without any overheads.
63- private static final int THUMBNAILER_WAIT_IN_MS = 2000;
64- private static final int DEFAULT_THUMBNAIL_WIDTH = 128;
65- private static final int DEFAULT_THUMBNAIL_HEIGHT = 96;
66-
67- public static final String DEFAULT_IMAGE_SORT_ORDER = Images.ImageColumns.DATE_TAKEN + " ASC, "
68- + Images.ImageColumns.DATE_ADDED + " ASC";
69- public static final String DEFAULT_VIDEO_SORT_ORDER = Video.VideoColumns.DATE_TAKEN + " ASC, " + Video.VideoColumns.DATE_ADDED
70- + " ASC";
71- public static final String DEFAULT_BUCKET_SORT_ORDER = "upper(" + Images.ImageColumns.BUCKET_DISPLAY_NAME + ") ASC";
72-
73- // Must preserve order between these indices and the order of the terms in BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
74- // Not using SortedHashMap for efficieny reasons.
75- public static final int BUCKET_ID_INDEX = 0;
76- public static final int BUCKET_NAME_INDEX = 1;
77- public static final String[] BUCKET_PROJECTION_IMAGES = new String[] { Images.ImageColumns.BUCKET_ID,
78- Images.ImageColumns.BUCKET_DISPLAY_NAME };
79-
80- public static final String[] BUCKET_PROJECTION_VIDEOS = new String[] { Video.VideoColumns.BUCKET_ID,
81- Video.VideoColumns.BUCKET_DISPLAY_NAME };
82-
83- // Must preserve order between these indices and the order of the terms in THUMBNAIL_PROJECTION.
84- public static final int THUMBNAIL_ID_INDEX = 0;
85- public static final int THUMBNAIL_DATE_MODIFIED_INDEX = 1;
86- public static final int THUMBNAIL_DATA_INDEX = 2;
87- public static final int THUMBNAIL_ORIENTATION_INDEX = 2;
88- public static final String[] THUMBNAIL_PROJECTION = new String[] { Images.ImageColumns._ID, Images.ImageColumns.DATE_MODIFIED,
89- Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION };
90-
91- // Must preserve order between these indices and the order of the terms in INITIAL_PROJECTION_IMAGES and
92- // INITIAL_PROJECTION_VIDEOS.
93- public static final int MEDIA_ID_INDEX = 0;
94- public static final int MEDIA_CAPTION_INDEX = 1;
95- public static final int MEDIA_MIME_TYPE_INDEX = 2;
96- public static final int MEDIA_LATITUDE_INDEX = 3;
97- public static final int MEDIA_LONGITUDE_INDEX = 4;
98- public static final int MEDIA_DATE_TAKEN_INDEX = 5;
99- public static final int MEDIA_DATE_ADDED_INDEX = 6;
100- public static final int MEDIA_DATE_MODIFIED_INDEX = 7;
101- public static final int MEDIA_DATA_INDEX = 8;
102- public static final int MEDIA_ORIENTATION_OR_DURATION_INDEX = 9;
103- public static final int MEDIA_BUCKET_ID_INDEX = 10;
104- public static final String[] PROJECTION_IMAGES = new String[] { Images.ImageColumns._ID, Images.ImageColumns.TITLE,
105- Images.ImageColumns.MIME_TYPE, Images.ImageColumns.LATITUDE, Images.ImageColumns.LONGITUDE,
106- Images.ImageColumns.DATE_TAKEN, Images.ImageColumns.DATE_ADDED, Images.ImageColumns.DATE_MODIFIED,
107- Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION, Images.ImageColumns.BUCKET_ID };
108-
109- private static final String[] PROJECTION_VIDEOS = new String[] { Video.VideoColumns._ID, Video.VideoColumns.TITLE,
110- Video.VideoColumns.MIME_TYPE, Video.VideoColumns.LATITUDE, Video.VideoColumns.LONGITUDE, Video.VideoColumns.DATE_TAKEN,
111- Video.VideoColumns.DATE_ADDED, Video.VideoColumns.DATE_MODIFIED, Video.VideoColumns.DATA, Video.VideoColumns.DURATION,
112- Video.VideoColumns.BUCKET_ID };
113-
114- public static final String BASE_CONTENT_STRING_IMAGES = (Images.Media.EXTERNAL_CONTENT_URI).toString() + "/";
115- public static final String BASE_CONTENT_STRING_VIDEOS = (Video.Media.EXTERNAL_CONTENT_URI).toString() + "/";
116- private static final AtomicReference<Thread> CACHE_THREAD = new AtomicReference<Thread>();
117- private static final AtomicReference<Thread> THUMBNAIL_THREAD = new AtomicReference<Thread>();
118-
119- // Special indices in the Albumcache.
120- private static final int ALBUM_CACHE_METADATA_INDEX = -1;
121- private static final int ALBUM_CACHE_DIRTY_INDEX = -2;
122- private static final int ALBUM_CACHE_INCOMPLETE_INDEX = -3;
123- private static final int ALBUM_CACHE_DIRTY_BUCKET_INDEX = -4;
124- private static final int ALBUM_CACHE_LOCALE_INDEX = -5;
125-
126- private static final DateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
127- private static final DateFormat mAltDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
128- private static final byte[] sDummyData = new byte[] { 1 };
129- private static boolean QUEUE_DIRTY_SET;
130- private static boolean QUEUE_DIRTY_ALL;
131-
132- public static final String getCachePath(final String subFolderName) {
133- return Environment.getExternalStorageDirectory() + "/Android/data/com.cooliris.media/cache/" + subFolderName;
134- }
135-
136- public static final void startCache(final Context context, final boolean checkthumbnails) {
137- final Locale locale = getLocaleForAlbumCache();
138- final Locale defaultLocale = Locale.getDefault();
139- if (locale == null || !locale.equals(defaultLocale)) {
140- sAlbumCache.deleteAll();
141- putLocaleForAlbumCache(defaultLocale);
142- }
143- final Intent intent = new Intent(ACTION_CACHE, null, context, CacheService.class);
144- intent.putExtra("checkthumbnails", checkthumbnails);
145- context.startService(intent);
146- }
147-
148- public static final boolean isCacheReady(final boolean onlyMediaSets) {
149- if (onlyMediaSets) {
150- return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null);
151- } else {
152- return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache
153- .get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
154- }
155- }
156-
157- public static final boolean isCacheReady(final long setId) {
158- final boolean isReady = (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null
159- && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache.get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
160- if (!isReady) {
161- return isReady;
162- }
163- // Also, we need to check if this setId is dirty.
164- final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
165- if (existingData != null && existingData.length > 0) {
166- final long[] ids = toLongArray(existingData);
167- final int numIds = ids.length;
168- for (int i = 0; i < numIds; ++i) {
169- if (ids[i] == setId) {
170- return false;
171- }
172- }
173- }
174- return true;
175- }
176-
177- public static final boolean isPresentInCache(final long setId) {
178- return sAlbumCache.get(setId, 0) != null;
179- }
180-
181- public static final void markDirty(final Context context) {
182- sList = null;
183- sAlbumCache.put(ALBUM_CACHE_DIRTY_INDEX, sDummyData);
184- if (CACHE_THREAD.get() == null) {
185- restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
186- public void run() {
187- refresh(context);
188- }
189- });
190- } else {
191- QUEUE_DIRTY_ALL = true;
192- }
193- }
194-
195- public static final void markDirtyImmediate(final long id) {
196- sList = null;
197- byte[] data = longToByteArray(id);
198- final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
199- if (existingData != null && existingData.length > 0) {
200- final long[] ids = toLongArray(existingData);
201- final int numIds = ids.length;
202- for (int i = 0; i < numIds; ++i) {
203- if (ids[i] == id) {
204- return;
205- }
206- }
207- // Add this to the existing keys and concatenate the byte arrays.
208- data = concat(data, existingData);
209- }
210- sAlbumCache.put(ALBUM_CACHE_DIRTY_BUCKET_INDEX, data);
211- }
212-
213- public static final void markDirty(final Context context, final long id) {
214- markDirtyImmediate(id);
215- if (CACHE_THREAD.get() == null) {
216- restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
217- public void run() {
218- refreshDirtySets(context);
219- }
220- });
221- } else {
222- QUEUE_DIRTY_SET = true;
223- }
224- }
225-
226- public static final boolean setHasItems(final ContentResolver cr, final long setId) {
227- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
228- final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
229- final StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + "=" + setId);
230- final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, whereString.toString(), null, null);
231- if (cursorImages != null && cursorImages.getCount() > 0) {
232- cursorImages.close();
233- return true;
234- }
235- final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, whereString.toString(), null, null);
236- if (cursorVideos != null && cursorVideos.getCount() > 0) {
237- cursorVideos.close();
238- return true;
239- }
240- return false;
241- }
242-
243- public static final void loadMediaSets(final MediaFeed feed, final DataSource source, final boolean includeImages,
244- final boolean includeVideos) {
245- int timeElapsed = 0;
246- while (!isCacheReady(true) && timeElapsed < 10000) {
247- try {
248- Thread.sleep(300);
249- } catch (InterruptedException e) {
250- return;
251- }
252- timeElapsed += 300;
253- }
254- final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
255- if (albumData != null && albumData.length > 0) {
256- final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
257- try {
258- final int numAlbums = dis.readInt();
259- Log.i(TAG, "Loading " + numAlbums + " albums.");
260- for (int i = 0; i < numAlbums; ++i) {
261- final long setId = dis.readLong();
262- final String name = Utils.readUTF(dis);
263- final boolean hasImages = dis.readBoolean();
264- final boolean hasVideos = dis.readBoolean();
265- MediaSet mediaSet = feed.getMediaSet(setId);
266- if (mediaSet == null) {
267- mediaSet = feed.addMediaSet(setId, source);
268- }
269- if ((includeImages && hasImages) || (includeVideos && hasVideos)) {
270- mediaSet.mName = name;
271- mediaSet.mHasImages = hasImages;
272- mediaSet.mHasVideos = hasVideos;
273- mediaSet.mPicasaAlbumId = Shared.INVALID;
274- mediaSet.generateTitle(true);
275- }
276- }
277- } catch (IOException e) {
278- Log.e(TAG, "Error loading albums.");
279- sAlbumCache.deleteAll();
280- putLocaleForAlbumCache(Locale.getDefault());
281- }
282- } else {
283- Log.d(TAG, "No albums found.");
284- }
285- }
286-
287- public static final void loadMediaSet(final MediaFeed feed, final DataSource source, final long bucketId) {
288- int timeElapsed = 0;
289- while (!isCacheReady(false) && timeElapsed < 10000) {
290- try {
291- Thread.sleep(300);
292- } catch (InterruptedException e) {
293- return;
294- }
295- timeElapsed += 300;
296- }
297- final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
298- if (albumData != null && albumData.length > 0) {
299- DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
300- try {
301- final int numAlbums = dis.readInt();
302- for (int i = 0; i < numAlbums; ++i) {
303- final long setId = dis.readLong();
304- MediaSet mediaSet = null;
305- if (setId == bucketId) {
306- mediaSet = feed.getMediaSet(setId);
307- if (mediaSet == null) {
308- mediaSet = feed.addMediaSet(setId, source);
309- }
310- } else {
311- mediaSet = new MediaSet();
312- }
313- mediaSet.mName = Utils.readUTF(dis);
314- if (setId == bucketId) {
315- mediaSet.mPicasaAlbumId = Shared.INVALID;
316- mediaSet.generateTitle(true);
317- return;
318- }
319- }
320- } catch (IOException e) {
321- Log.e(TAG, "Error finding album " + bucketId);
322- sAlbumCache.deleteAll();
323- putLocaleForAlbumCache(Locale.getDefault());
324- }
325- } else {
326- Log.d(TAG, "No album found for album id " + bucketId);
327- }
328- }
329-
330- public static final void loadMediaItemsIntoMediaFeed(final MediaFeed feed, final MediaSet set, final int rangeStart,
331- final int rangeEnd, final boolean includeImages, final boolean includeVideos) {
332- int timeElapsed = 0;
333- byte[] albumData = null;
334- while (!isCacheReady(set.mId) && timeElapsed < 30000) {
335- try {
336- Thread.sleep(300);
337- } catch (InterruptedException e) {
338- return;
339- }
340- timeElapsed += 300;
341- }
342- albumData = sAlbumCache.get(set.mId, 0);
343- if (albumData != null && set.mNumItemsLoaded < set.getNumExpectedItems()) {
344- final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
345- try {
346- final int numItems = dis.readInt();
347- Log.i(TAG, "Loading Set Id " + set.mId + " with " + numItems + " items.");
348- set.setNumExpectedItems(numItems);
349- set.mMinTimestamp = dis.readLong();
350- set.mMaxTimestamp = dis.readLong();
351- for (int i = 0; i < numItems; ++i) {
352- final MediaItem item = new MediaItem();
353- // Must preserve order with method that writes to cache.
354- item.mId = dis.readLong();
355- item.mCaption = Utils.readUTF(dis);
356- item.mMimeType = Utils.readUTF(dis);
357- item.setMediaType(dis.readInt());
358- item.mLatitude = dis.readDouble();
359- item.mLongitude = dis.readDouble();
360- item.mDateTakenInMs = dis.readLong();
361- item.mTriedRetrievingExifDateTaken = dis.readBoolean();
362- item.mDateAddedInSec = dis.readLong();
363- item.mDateModifiedInSec = dis.readLong();
364- item.mDurationInSec = dis.readInt();
365- item.mRotation = (float) dis.readInt();
366- item.mFilePath = Utils.readUTF(dis);
367- int itemMediaType = item.getMediaType();
368- if ((itemMediaType == MediaItem.MEDIA_TYPE_IMAGE && includeImages)
369- || (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO && includeVideos)) {
370- String baseUri = (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) ? BASE_CONTENT_STRING_IMAGES
371- : BASE_CONTENT_STRING_VIDEOS;
372- item.mContentUri = baseUri + item.mId;
373- feed.addItemToMediaSet(item, set);
374- }
375- }
376- dis.close();
377- } catch (IOException e) {
378- Log.e(TAG, "Error loading items for album " + set.mName);
379- sAlbumCache.deleteAll();
380- putLocaleForAlbumCache(Locale.getDefault());
381- }
382- } else {
383- Log.d(TAG, "No items found for album " + set.mName);
384- }
385- set.updateNumExpectedItems();
386- set.generateTitle(true);
387- }
388-
389- public static final void populateVideoItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
390- final String baseUri) {
391- item.setMediaType(MediaItem.MEDIA_TYPE_VIDEO);
392- populateMediaItemFromCursor(item, cr, cursor, baseUri);
393- }
394-
395- public static final void populateMediaItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
396- final String baseUri) {
397- item.mId = cursor.getLong(CacheService.MEDIA_ID_INDEX);
398- // item.mCaption = cursor.getString(CacheService.MEDIA_CAPTION_INDEX);
399- item.mMimeType = cursor.getString(CacheService.MEDIA_MIME_TYPE_INDEX);
400- item.mLatitude = cursor.getDouble(CacheService.MEDIA_LATITUDE_INDEX);
401- item.mLongitude = cursor.getDouble(CacheService.MEDIA_LONGITUDE_INDEX);
402- item.mDateTakenInMs = cursor.getLong(CacheService.MEDIA_DATE_TAKEN_INDEX);
403- item.mDateAddedInSec = cursor.getLong(CacheService.MEDIA_DATE_ADDED_INDEX);
404- item.mDateModifiedInSec = cursor.getLong(CacheService.MEDIA_DATE_MODIFIED_INDEX);
405- if (item.mDateTakenInMs == item.mDateModifiedInSec) {
406- item.mDateTakenInMs = item.mDateModifiedInSec * 1000;
407- }
408- item.mFilePath = cursor.getString(CacheService.MEDIA_DATA_INDEX);
409- if (baseUri != null)
410- item.mContentUri = baseUri + item.mId;
411- final int itemMediaType = item.getMediaType();
412- // Check to see if a new date taken is available.
413- final long dateTaken = fetchDateTaken(item);
414- if (dateTaken != -1L && item.mContentUri != null) {
415- item.mDateTakenInMs = dateTaken;
416- final ContentValues values = new ContentValues();
417- if (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
418- values.put(Video.VideoColumns.DATE_TAKEN, item.mDateTakenInMs);
419- } else {
420- values.put(Images.ImageColumns.DATE_TAKEN, item.mDateTakenInMs);
421- }
422- cr.update(Uri.parse(item.mContentUri), values, null, null);
423- }
424-
425- final int orientationDurationValue = cursor.getInt(CacheService.MEDIA_ORIENTATION_OR_DURATION_INDEX);
426- if (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
427- item.mRotation = orientationDurationValue;
428- } else {
429- item.mDurationInSec = orientationDurationValue;
430- }
431- }
432-
433- // Returns -1 if we failed to examine EXIF information or EXIF parsing failed.
434- public static final long fetchDateTaken(final MediaItem item) {
435- if (!item.isDateTakenValid() && !item.mTriedRetrievingExifDateTaken
436- && (item.mFilePath.endsWith(".jpg") || item.mFilePath.endsWith(".jpeg"))) {
437- try {
438- Log.i(TAG, "Parsing date taken from exif");
439- final ExifInterface exif = new ExifInterface(item.mFilePath);
440- final String dateTakenStr = exif.getAttribute(ExifInterface.TAG_DATETIME);
441- if (dateTakenStr != null) {
442- try {
443- final Date dateTaken = mDateFormat.parse(dateTakenStr);
444- return dateTaken.getTime();
445- } catch (ParseException pe) {
446- try {
447- final Date dateTaken = mAltDateFormat.parse(dateTakenStr);
448- return dateTaken.getTime();
449- } catch (ParseException pe2) {
450- Log.i(TAG, "Unable to parse date out of string - " + dateTakenStr);
451- }
452- }
453- }
454- } catch (Exception e) {
455- Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
456- }
457-
458- // Ensures that we only try retrieving EXIF date taken once.
459- item.mTriedRetrievingExifDateTaken = true;
460- }
461- return -1L;
462- }
463-
464- public static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
465- final long timestamp) {
466- final DiskCache thumbnailCache = (isVideo) ? LocalDataSource.sThumbnailCacheVideo : LocalDataSource.sThumbnailCache;
467- return queryThumbnail(context, thumbId, origId, isVideo, thumbnailCache, timestamp);
468- }
469-
470- public static final ImageList getImageList(final Context context) {
471- if (sList != null)
472- return sList;
473- ImageList list = new ImageList();
474- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
475- final ContentResolver cr = context.getContentResolver();
476- final Cursor cursorImages = cr.query(uriImages, THUMBNAIL_PROJECTION, null, null, null);
477- if (cursorImages != null && cursorImages.moveToFirst()) {
478- final int size = cursorImages.getCount();
479- final long[] ids = new long[size];
480- final long[] thumbnailIds = new long[size];
481- final long[] timestamp = new long[size];
482- final int[] orientation = new int[size];
483- int ctr = 0;
484- do {
485- if (Thread.interrupted()) {
486- break;
487- }
488- ids[ctr] = cursorImages.getLong(THUMBNAIL_ID_INDEX);
489- timestamp[ctr] = cursorImages.getLong(THUMBNAIL_DATE_MODIFIED_INDEX);
490- thumbnailIds[ctr] = Utils.Crc64Long(cursorImages.getString(THUMBNAIL_DATA_INDEX));
491- orientation[ctr] = cursorImages.getInt(THUMBNAIL_ORIENTATION_INDEX);
492- ++ctr;
493- } while (cursorImages.moveToNext());
494- cursorImages.close();
495- list.ids = ids;
496- list.thumbids = thumbnailIds;
497- list.timestamp = timestamp;
498- list.orientation = orientation;
499- }
500- if (sList == null) {
501- sList = list;
502- }
503- return list;
504- }
505-
506- private static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
507- final DiskCache thumbnailCache, final long timestamp) {
508- if (!((Gallery) context).isPaused()) {
509- final Thread thumbnailThread = THUMBNAIL_THREAD.getAndSet(null);
510- if (thumbnailThread != null) {
511- thumbnailThread.interrupt();
512- }
513- }
514- byte[] bitmap = thumbnailCache.get(thumbId, timestamp);
515- if (bitmap == null) {
516- Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
517- final long time = SystemClock.uptimeMillis();
518- bitmap = buildThumbnailForId(context, thumbnailCache, thumbId, origId, isVideo, DEFAULT_THUMBNAIL_WIDTH,
519- DEFAULT_THUMBNAIL_HEIGHT);
520- Log.i(TAG, "Built thumbnail and screennail for " + origId + " in " + (SystemClock.uptimeMillis() - time));
521- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
522- }
523- return bitmap;
524- }
525-
526- private static final void buildThumbnails(final Context context) {
527- Log.i(TAG, "Preparing DiskCache for all thumbnails.");
528- ImageList list = getImageList(context);
529- final int size = (list.ids == null) ? 0 : list.ids.length;
530- final long[] ids = list.ids;
531- final long[] timestamp = list.timestamp;
532- final long[] thumbnailIds = list.thumbids;
533- final DiskCache thumbnailCache = LocalDataSource.sThumbnailCache;
534- for (int i = 0; i < size; ++i) {
535- if (Thread.interrupted()) {
536- return;
537- }
538- final long id = ids[i];
539- final long timeModifiedInSec = timestamp[i];
540- final long thumbnailId = thumbnailIds[i];
541- if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
542- buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
543- DEFAULT_THUMBNAIL_HEIGHT);
544- }
545- }
546- Log.i(TAG, "DiskCache ready for all thumbnails.");
547- }
548-
549- private static final byte[] buildThumbnailForId(final Context context, final DiskCache thumbnailCache, final long thumbId,
550- final long origId, final boolean isVideo, final int thumbnailWidth, final int thumbnailHeight) {
551- if (origId == Shared.INVALID) {
552- return null;
553- }
554- try {
555- Bitmap bitmap = null;
556- Thread.sleep(1);
557- if (!isVideo) {
558- final String uriString = BASE_CONTENT_STRING_IMAGES + origId;
559- UriTexture.invalidateCache(thumbId, 1024);
560- try {
561- bitmap = UriTexture.createFromUri(context, uriString, 1024, 1024, thumbId, null);
562- } catch (IOException e) {
563- return null;
564- } catch (URISyntaxException e) {
565- return null;
566- }
567- } else {
568- Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
569- new Thread() {
570- public void run() {
571- try {
572- Thread.sleep(5000);
573- } catch (InterruptedException e) {
574- ;
575- }
576- try {
577- MediaStore.Video.Thumbnails.cancelThumbnailRequest(context.getContentResolver(), origId);
578- } catch (Exception e) {
579- ;
580- }
581- }
582- }.start();
583- bitmap = MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), origId,
584- MediaStore.Video.Thumbnails.MICRO_KIND, null);
585- }
586- if (bitmap == null) {
587- return null;
588- }
589- final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight);
590- return retVal;
591- } catch (InterruptedException e) {
592- return null;
593- }
594- }
595-
596- public static final byte[] writeBitmapToCache(final DiskCache thumbnailCache, final long thumbId, final long origId,
597- final Bitmap bitmap, final int thumbnailWidth, final int thumbnailHeight) {
598- final int width = bitmap.getWidth();
599- final int height = bitmap.getHeight();
600- // Detect faces to find the focal point, otherwise fall back to the image center.
601- int focusX = width / 2;
602- int focusY = height / 2;
603- // We have commented out face detection since it slows down the generation of the thumbnail and screennail.
604-
605- // final FaceDetector faceDetector = new FaceDetector(width, height, 1);
606- // final FaceDetector.Face[] faces = new FaceDetector.Face[1];
607- // final int numFaces = faceDetector.findFaces(bitmap, faces);
608- // if (numFaces > 0 && faces[0].confidence() >= FaceDetector.Face.CONFIDENCE_THRESHOLD) {
609- // final PointF midPoint = new PointF();
610- // faces[0].getMidPoint(midPoint);
611- // focusX = (int) midPoint.x;
612- // focusY = (int) midPoint.y;
613- // }
614-
615- // Crop to thumbnail aspect ratio biased towards the focus point.
616- int cropX;
617- int cropY;
618- int cropWidth;
619- int cropHeight;
620- float scaleFactor;
621- if (thumbnailWidth * height < thumbnailHeight * width) {
622- // Vertically constrained.
623- cropWidth = thumbnailWidth * height / thumbnailHeight;
624- cropX = Math.max(0, Math.min(focusX - cropWidth / 2, width - cropWidth));
625- cropY = 0;
626- cropHeight = height;
627- scaleFactor = (float) thumbnailHeight / height;
628- } else {
629- // Horizontally constrained.
630- cropHeight = thumbnailHeight * width / thumbnailWidth;
631- cropY = Math.max(0, Math.min(focusY - cropHeight / 2, height - cropHeight));
632- cropX = 0;
633- cropWidth = width;
634- scaleFactor = (float) thumbnailWidth / width;
635- }
636- final Bitmap finalBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.RGB_565);
637- final Canvas canvas = new Canvas(finalBitmap);
638- final Paint paint = new Paint();
639- paint.setFilterBitmap(true);
640- canvas.drawColor(0);
641- canvas.drawBitmap(bitmap, new Rect(cropX, cropY, cropX + cropWidth, cropY + cropHeight), new Rect(0, 0, thumbnailWidth,
642- thumbnailHeight), paint);
643- bitmap.recycle();
644-
645- // Store (long thumbnailId, short focusX, short focusY, JPEG data).
646- final ByteArrayOutputStream cacheOutput = new ByteArrayOutputStream(16384);
647- final DataOutputStream dataOutput = new DataOutputStream(cacheOutput);
648- byte[] retVal = null;
649- try {
650- dataOutput.writeLong(origId);
651- dataOutput.writeShort((int) ((focusX - cropX) * scaleFactor));
652- dataOutput.writeShort((int) ((focusY - cropY) * scaleFactor));
653- dataOutput.flush();
654- finalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, cacheOutput);
655- retVal = cacheOutput.toByteArray();
656- synchronized (thumbnailCache) {
657- thumbnailCache.put(thumbId, retVal);
658- }
659- cacheOutput.close();
660- } catch (Exception e) {
661- ;
662- }
663- return retVal;
664- }
665-
666- public CacheService() {
667- super("CacheService");
668- }
669-
670- @Override
671- protected void onHandleIntent(final Intent intent) {
672- Log.i(TAG, "Starting CacheService");
673- if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) {
674- sAlbumCache.deleteAll();
675- putLocaleForAlbumCache(Locale.getDefault());
676- }
677- Locale locale = getLocaleForAlbumCache();
678- if (locale != null && locale.equals(Locale.getDefault())) {
679- // The cache is in the same locale as the system locale.
680- if (!isCacheReady(false)) {
681- // The albums and their items have not yet been cached, we need to run the service.
682- startNewCacheThread();
683- } else {
684- startNewCacheThreadForDirtySets();
685- }
686- } else {
687- // The locale has changed, we need to regenerate the strings.
688- sAlbumCache.deleteAll();
689- putLocaleForAlbumCache(Locale.getDefault());
690- startNewCacheThread();
691- }
692- if (intent.getBooleanExtra("checkthumbnails", false)) {
693- startNewThumbnailThread(this);
694- } else {
695- final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null);
696- if (existingThread != null) {
697- existingThread.interrupt();
698- }
699- }
700- }
701-
702- private static final void putLocaleForAlbumCache(final Locale locale) {
703- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
704- final DataOutputStream dos = new DataOutputStream(bos);
705- try {
706- Utils.writeUTF(dos, locale.getCountry());
707- Utils.writeUTF(dos, locale.getLanguage());
708- Utils.writeUTF(dos, locale.getVariant());
709- dos.flush();
710- bos.flush();
711- final byte[] data = bos.toByteArray();
712- sAlbumCache.put(ALBUM_CACHE_LOCALE_INDEX, data);
713- sAlbumCache.flush();
714- dos.close();
715- bos.close();
716- } catch (IOException e) {
717- // Could not write locale to cache.
718- Log.i(TAG, "Error writing locale to cache.");
719- ;
720- }
721- }
722-
723- private static final Locale getLocaleForAlbumCache() {
724- final byte[] data = sAlbumCache.get(ALBUM_CACHE_LOCALE_INDEX, 0);
725- if (data != null && data.length > 0) {
726- ByteArrayInputStream bis = new ByteArrayInputStream(data);
727- DataInputStream dis = new DataInputStream(bis);
728- try {
729- String country = Utils.readUTF(dis);
730- if (country == null)
731- country = "";
732- String language = Utils.readUTF(dis);
733- if (language == null)
734- language = "";
735- String variant = Utils.readUTF(dis);
736- if (variant == null)
737- variant = "";
738- final Locale locale = new Locale(language, country, variant);
739- dis.close();
740- bis.close();
741- return locale;
742- } catch (IOException e) {
743- // Could not read locale in cache.
744- Log.i(TAG, "Error reading locale from cache.");
745- return null;
746- }
747- }
748- return null;
749- }
750-
751- private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) {
752- // Create a new thread.
753- final Thread newThread = new Thread() {
754- public void run() {
755- try {
756- action.run();
757- } finally {
758- threadRef.compareAndSet(this, null);
759- }
760- }
761- };
762- newThread.setName(name);
763- newThread.start();
764-
765- // Interrupt any existing thread.
766- final Thread existingThread = threadRef.getAndSet(newThread);
767- if (existingThread != null) {
768- existingThread.interrupt();
769- }
770- }
771-
772- public static final void startNewThumbnailThread(final Context context) {
773- restartThread(THUMBNAIL_THREAD, "ThumbnailRefresh", new Runnable() {
774- public void run() {
775- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
776- try {
777- // It is an optimization to prevent the thumbnailer from running while the application loads
778- Thread.sleep(THUMBNAILER_WAIT_IN_MS);
779- } catch (InterruptedException e) {
780- return;
781- }
782- CacheService.buildThumbnails(context);
783- }
784- });
785- }
786-
787- private void startNewCacheThread() {
788- restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
789- public void run() {
790- refresh(CacheService.this);
791- }
792- });
793- }
794-
795- private void startNewCacheThreadForDirtySets() {
796- restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
797- public void run() {
798- refreshDirtySets(CacheService.this);
799- }
800- });
801- }
802-
803- private static final byte[] concat(final byte[] A, final byte[] B) {
804- final byte[] C = (byte[]) new byte[A.length + B.length];
805- System.arraycopy(A, 0, C, 0, A.length);
806- System.arraycopy(B, 0, C, A.length, B.length);
807- return C;
808- }
809-
810- private static final long[] toLongArray(final byte[] data) {
811- final ByteBuffer bBuffer = ByteBuffer.wrap(data);
812- final LongBuffer lBuffer = bBuffer.asLongBuffer();
813- final int numLongs = lBuffer.capacity();
814- final long[] retVal = new long[numLongs];
815- for (int i = 0; i < numLongs; ++i) {
816- retVal[i] = lBuffer.get(i);
817- }
818- return retVal;
819- }
820-
821- private static final byte[] longToByteArray(final long l) {
822- final byte[] bArray = new byte[8];
823- final ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
824- final LongBuffer lBuffer = bBuffer.asLongBuffer();
825- lBuffer.put(0, l);
826- return bArray;
827- }
828-
829- private final static void refresh(final Context context) {
830- // First we build the album cache.
831- // This is the meta-data about the albums / buckets on the SD card.
832- Log.i(TAG, "Refreshing cache.");
833- int priority = Process.getThreadPriority(Process.myTid());
834- Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);
835- sAlbumCache.deleteAll();
836- putLocaleForAlbumCache(Locale.getDefault());
837-
838- final ArrayList<MediaSet> sets = new ArrayList<MediaSet>();
839- LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>();
840- Log.i(TAG, "Building albums.");
841- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
842- final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
843- final ContentResolver cr = context.getContentResolver();
844-
845- final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);
846- final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);
847- Cursor[] cursors = new Cursor[2];
848- cursors[0] = cursorImages;
849- cursors[1] = cursorVideos;
850- final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true);
851- try {
852- if (sortCursor != null && sortCursor.moveToFirst()) {
853- sets.ensureCapacity(sortCursor.getCount());
854- acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount());
855- MediaSet cameraSet = new MediaSet();
856- cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;
857- cameraSet.mName = context.getResources().getString(R.string.camera);
858- sets.add(cameraSet);
859- acceleratedSets.put(cameraSet.mId, cameraSet);
860- do {
861- if (Thread.interrupted()) {
862- return;
863- }
864- long setId = sortCursor.getLong(BUCKET_ID_INDEX);
865- MediaSet mediaSet = findSet(setId, acceleratedSets);
866- if (mediaSet == null) {
867- mediaSet = new MediaSet();
868- mediaSet.mId = setId;
869- mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX);
870- sets.add(mediaSet);
871- acceleratedSets.put(setId, mediaSet);
872- }
873- mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0);
874- mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1);
875- } while (sortCursor.moveToNext());
876- sortCursor.close();
877- }
878- } finally {
879- if (sortCursor != null)
880- sortCursor.close();
881- }
882-
883- sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData);
884- writeSetsToCache(sets);
885- Log.i(TAG, "Done building albums.");
886- // Now we must cache the items contained in every album / bucket.
887- populateMediaItemsForSets(context, sets, acceleratedSets, false);
888- sAlbumCache.delete(ALBUM_CACHE_INCOMPLETE_INDEX);
889- Process.setThreadPriority(priority);
890- if (QUEUE_DIRTY_ALL) {
891- QUEUE_DIRTY_ALL = false;
892- refresh(context);
893- }
894- }
895-
896- private final static void refreshDirtySets(final Context context) {
897- final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
898- if (existingData != null && existingData.length > 0) {
899- final long[] ids = toLongArray(existingData);
900- final int numIds = ids.length;
901- if (numIds > 0) {
902- final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(numIds);
903- final LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(numIds);
904- for (int i = 0; i < numIds; ++i) {
905- final MediaSet set = new MediaSet();
906- set.mId = ids[i];
907- sets.add(set);
908- acceleratedSets.put(set.mId, set);
909- }
910- Log.i(TAG, "Refreshing dirty albums");
911- populateMediaItemsForSets(context, sets, acceleratedSets, true);
912- }
913- }
914- if (QUEUE_DIRTY_SET) {
915- QUEUE_DIRTY_SET = false;
916- refreshDirtySets(context);
917- } else {
918- sAlbumCache.delete(ALBUM_CACHE_DIRTY_BUCKET_INDEX);
919- }
920- }
921-
922- private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets,
923- final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) {
924- if (sets == null || sets.size() == 0 || Thread.interrupted()) {
925- return;
926- }
927- Log.i(TAG, "Building items.");
928- final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
929- final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
930- final ContentResolver cr = context.getContentResolver();
931-
932- String whereClause = null;
933- if (useWhere) {
934- int numSets = sets.size();
935- StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in (");
936- for (int i = 0; i < numSets; ++i) {
937- whereString.append(sets.get(i).mId);
938- if (i != numSets - 1) {
939- whereString.append(",");
940- }
941- }
942- whereString.append(")");
943- whereClause = whereString.toString();
944- Log.i(TAG, "Updating dirty albums where " + whereClause);
945- }
946-
947- final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER);
948- final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER);
949- final Cursor[] cursors = new Cursor[2];
950- cursors[0] = cursorImages;
951- cursors[1] = cursorVideos;
952- final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true);
953- if (Thread.interrupted()) {
954- return;
955- }
956- try {
957- if (sortCursor != null && sortCursor.moveToFirst()) {
958- final int count = sortCursor.getCount();
959- final int numSets = sets.size();
960- final int approximateCountPerSet = count / numSets;
961- for (int i = 0; i < numSets; ++i) {
962- final MediaSet set = sets.get(i);
963- set.getItems().clear();
964- set.setNumExpectedItems(approximateCountPerSet);
965- }
966- do {
967- if (Thread.interrupted()) {
968- return;
969- }
970- final MediaItem item = new MediaItem();
971- final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1);
972- if (isVideo) {
973- populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);
974- } else {
975- populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);
976- }
977- final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX);
978- final MediaSet set = findSet(setId, acceleratedSets);
979- if (set != null) {
980- set.getItems().add(item);
981- }
982- } while (sortCursor.moveToNext());
983- }
984- } finally {
985- if (sortCursor != null) sortCursor.close();
986- }
987- if (sets.size() > 0) {
988- writeItemsToCache(sets);
989- Log.i(TAG, "Done building items.");
990- }
991- }
992-
993- private static final void writeSetsToCache(final ArrayList<MediaSet> sets) {
994- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
995- final int numSets = sets.size();
996- final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
997- try {
998- dos.writeInt(numSets);
999- for (int i = 0; i < numSets; ++i) {
1000- if (Thread.interrupted()) {
1001- return;
1002- }
1003- final MediaSet set = sets.get(i);
1004- dos.writeLong(set.mId);
1005- Utils.writeUTF(dos, set.mName);
1006- dos.writeBoolean(set.mHasImages);
1007- dos.writeBoolean(set.mHasVideos);
1008- }
1009- dos.flush();
1010- sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray());
1011- dos.close();
1012- if (numSets == 0) {
1013- sAlbumCache.deleteAll();
1014- putLocaleForAlbumCache(Locale.getDefault());
1015- }
1016- sAlbumCache.flush();
1017- } catch (IOException e) {
1018- Log.e(TAG, "Error writing albums to diskcache.");
1019- sAlbumCache.deleteAll();
1020- putLocaleForAlbumCache(Locale.getDefault());
1021- }
1022- }
1023-
1024- private static final void writeItemsToCache(final ArrayList<MediaSet> sets) {
1025- final int numSets = sets.size();
1026- for (int i = 0; i < numSets; ++i) {
1027- if (Thread.interrupted()) {
1028- return;
1029- }
1030- writeItemsForASet(sets.get(i));
1031- }
1032- sAlbumCache.flush();
1033- }
1034-
1035- private static final void writeItemsForASet(final MediaSet set) {
1036- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
1037- final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
1038- try {
1039- final ArrayList<MediaItem> items = set.getItems();
1040- final int numItems = items.size();
1041- dos.writeInt(numItems);
1042- dos.writeLong(set.mMinTimestamp);
1043- dos.writeLong(set.mMaxTimestamp);
1044- for (int i = 0; i < numItems; ++i) {
1045- MediaItem item = items.get(i);
1046- if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) {
1047- // Reverse the display order for the camera bucket - want the latest first.
1048- item = items.get(numItems - i - 1);
1049- }
1050- dos.writeLong(item.mId);
1051- Utils.writeUTF(dos, item.mCaption);
1052- Utils.writeUTF(dos, item.mMimeType);
1053- dos.writeInt(item.getMediaType());
1054- dos.writeDouble(item.mLatitude);
1055- dos.writeDouble(item.mLongitude);
1056- dos.writeLong(item.mDateTakenInMs);
1057- dos.writeBoolean(item.mTriedRetrievingExifDateTaken);
1058- dos.writeLong(item.mDateAddedInSec);
1059- dos.writeLong(item.mDateModifiedInSec);
1060- dos.writeInt(item.mDurationInSec);
1061- dos.writeInt((int) item.mRotation);
1062- Utils.writeUTF(dos, item.mFilePath);
1063- }
1064- dos.flush();
1065- sAlbumCache.put(set.mId, bos.toByteArray());
1066- dos.close();
1067- } catch (IOException e) {
1068- Log.e(TAG, "Error writing to diskcache for set " + set.mName);
1069- sAlbumCache.deleteAll();
1070- putLocaleForAlbumCache(Locale.getDefault());
1071- }
1072- }
1073-
1074- private static final MediaSet findSet(final long id, final LongSparseArray<MediaSet> acceleratedTable) {
1075- // This is the accelerated lookup table for the MediaSet based on set id.
1076- return acceleratedTable.get(id);
1077- }
57+ public static final String ACTION_CACHE = "com.cooliris.cache.action.CACHE";
58+ public static final DiskCache sAlbumCache = new DiskCache("local-album-cache");
59+ public static final DiskCache sMetaAlbumCache = new DiskCache("local-meta-album-cache");
60+
61+ private static final String TAG = "CacheService";
62+ private static ImageList sList = null;
63+
64+ // Wait 2 seconds to start the thumbnailer so that the application can load
65+ // without any overheads.
66+ private static final int THUMBNAILER_WAIT_IN_MS = 2000;
67+ private static final int DEFAULT_THUMBNAIL_WIDTH = 128;
68+ private static final int DEFAULT_THUMBNAIL_HEIGHT = 96;
69+
70+ public static final String DEFAULT_IMAGE_SORT_ORDER = Images.ImageColumns.DATE_TAKEN + " ASC, "
71+ + Images.ImageColumns.DATE_ADDED + " ASC";
72+ public static final String DEFAULT_VIDEO_SORT_ORDER = Video.VideoColumns.DATE_TAKEN + " ASC, " + Video.VideoColumns.DATE_ADDED
73+ + " ASC";
74+ public static final String DEFAULT_BUCKET_SORT_ORDER = "upper(" + Images.ImageColumns.BUCKET_DISPLAY_NAME + ") ASC";
75+
76+ // Must preserve order between these indices and the order of the terms in
77+ // BUCKET_PROJECTION_IMAGES, BUCKET_PROJECTION_VIDEOS.
78+ // Not using SortedHashMap for efficieny reasons.
79+ public static final int BUCKET_ID_INDEX = 0;
80+ public static final int BUCKET_NAME_INDEX = 1;
81+ public static final String[] BUCKET_PROJECTION_IMAGES = new String[] { Images.ImageColumns.BUCKET_ID,
82+ Images.ImageColumns.BUCKET_DISPLAY_NAME };
83+
84+ public static final String[] BUCKET_PROJECTION_VIDEOS = new String[] { Video.VideoColumns.BUCKET_ID,
85+ Video.VideoColumns.BUCKET_DISPLAY_NAME };
86+
87+ // Must preserve order between these indices and the order of the terms in
88+ // THUMBNAIL_PROJECTION.
89+ public static final int THUMBNAIL_ID_INDEX = 0;
90+ public static final int THUMBNAIL_DATE_MODIFIED_INDEX = 1;
91+ public static final int THUMBNAIL_DATA_INDEX = 2;
92+ public static final int THUMBNAIL_ORIENTATION_INDEX = 2;
93+ public static final String[] THUMBNAIL_PROJECTION = new String[] { Images.ImageColumns._ID, Images.ImageColumns.DATE_MODIFIED,
94+ Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION };
95+
96+ public static final String[] SENSE_PROJECTION = new String[] { Images.ImageColumns.BUCKET_ID,
97+ "MAX(" + Images.ImageColumns.DATE_ADDED + ")" };
98+
99+ // Must preserve order between these indices and the order of the terms in
100+ // INITIAL_PROJECTION_IMAGES and
101+ // INITIAL_PROJECTION_VIDEOS.
102+ public static final int MEDIA_ID_INDEX = 0;
103+ public static final int MEDIA_CAPTION_INDEX = 1;
104+ public static final int MEDIA_MIME_TYPE_INDEX = 2;
105+ public static final int MEDIA_LATITUDE_INDEX = 3;
106+ public static final int MEDIA_LONGITUDE_INDEX = 4;
107+ public static final int MEDIA_DATE_TAKEN_INDEX = 5;
108+ public static final int MEDIA_DATE_ADDED_INDEX = 6;
109+ public static final int MEDIA_DATE_MODIFIED_INDEX = 7;
110+ public static final int MEDIA_DATA_INDEX = 8;
111+ public static final int MEDIA_ORIENTATION_OR_DURATION_INDEX = 9;
112+ public static final int MEDIA_BUCKET_ID_INDEX = 10;
113+ public static final String[] PROJECTION_IMAGES = new String[] { Images.ImageColumns._ID, Images.ImageColumns.TITLE,
114+ Images.ImageColumns.MIME_TYPE, Images.ImageColumns.LATITUDE, Images.ImageColumns.LONGITUDE,
115+ Images.ImageColumns.DATE_TAKEN, Images.ImageColumns.DATE_ADDED, Images.ImageColumns.DATE_MODIFIED,
116+ Images.ImageColumns.DATA, Images.ImageColumns.ORIENTATION, Images.ImageColumns.BUCKET_ID };
117+
118+ private static final String[] PROJECTION_VIDEOS = new String[] { Video.VideoColumns._ID, Video.VideoColumns.TITLE,
119+ Video.VideoColumns.MIME_TYPE, Video.VideoColumns.LATITUDE, Video.VideoColumns.LONGITUDE, Video.VideoColumns.DATE_TAKEN,
120+ Video.VideoColumns.DATE_ADDED, Video.VideoColumns.DATE_MODIFIED, Video.VideoColumns.DATA, Video.VideoColumns.DURATION,
121+ Video.VideoColumns.BUCKET_ID };
122+
123+ public static final String BASE_CONTENT_STRING_IMAGES = (Images.Media.EXTERNAL_CONTENT_URI).toString() + "/";
124+ public static final String BASE_CONTENT_STRING_VIDEOS = (Video.Media.EXTERNAL_CONTENT_URI).toString() + "/";
125+ private static final AtomicReference<Thread> CACHE_THREAD = new AtomicReference<Thread>();
126+ private static final AtomicReference<Thread> THUMBNAIL_THREAD = new AtomicReference<Thread>();
127+
128+ // Special indices in the Albumcache.
129+ private static final int ALBUM_CACHE_METADATA_INDEX = -1;
130+ private static final int ALBUM_CACHE_DIRTY_INDEX = -2;
131+ private static final int ALBUM_CACHE_INCOMPLETE_INDEX = -3;
132+ private static final int ALBUM_CACHE_DIRTY_BUCKET_INDEX = -4;
133+ private static final int ALBUM_CACHE_LOCALE_INDEX = -5;
134+
135+ private static final DateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
136+ private static final DateFormat mAltDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
137+ private static final byte[] sDummyData = new byte[] { 1 };
138+ private static boolean QUEUE_DIRTY_SET;
139+ private static boolean QUEUE_DIRTY_ALL;
140+ private static boolean QUEUE_DIRTY_SENSE;
141+
142+ public interface Observer {
143+ void onChange(long[] bucketIds);
144+ }
145+
146+ public static final String getCachePath(final String subFolderName) {
147+ return Environment.getExternalStorageDirectory() + "/Android/data/com.cooliris.media/cache/" + subFolderName;
148+ }
149+
150+ public static final void startCache(final Context context, final boolean checkthumbnails) {
151+ final Locale locale = getLocaleForAlbumCache();
152+ final Locale defaultLocale = Locale.getDefault();
153+ if (locale == null || !locale.equals(defaultLocale)) {
154+ sAlbumCache.deleteAll();
155+ putLocaleForAlbumCache(defaultLocale);
156+ }
157+ final Intent intent = new Intent(ACTION_CACHE, null, context, CacheService.class);
158+ intent.putExtra("checkthumbnails", checkthumbnails);
159+ context.startService(intent);
160+ }
161+
162+ public static final boolean isCacheReady(final boolean onlyMediaSets) {
163+ if (onlyMediaSets) {
164+ return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null);
165+ } else {
166+ return (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache
167+ .get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
168+ }
169+ }
170+
171+ public static final boolean isCacheReady(final long setId) {
172+ final boolean isReady = (sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0) != null
173+ && sAlbumCache.get(ALBUM_CACHE_DIRTY_INDEX, 0) == null && sAlbumCache.get(ALBUM_CACHE_INCOMPLETE_INDEX, 0) == null);
174+ if (!isReady) {
175+ return isReady;
176+ }
177+ // Also, we need to check if this setId is dirty.
178+ final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
179+ if (existingData != null && existingData.length > 0) {
180+ final long[] ids = toLongArray(existingData);
181+ final int numIds = ids.length;
182+ for (int i = 0; i < numIds; ++i) {
183+ if (ids[i] == setId) {
184+ return false;
185+ }
186+ }
187+ }
188+ return true;
189+ }
190+
191+ public static final boolean isPresentInCache(final long setId) {
192+ return sAlbumCache.get(setId, 0) != null;
193+ }
194+
195+ public static final void senseDirty(final Context context, final Observer observer) {
196+ if (CACHE_THREAD.get() == null) {
197+ QUEUE_DIRTY_SENSE = false;
198+ QUEUE_DIRTY_ALL = false;
199+ QUEUE_DIRTY_SET = false;
200+ restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
201+ public void run() {
202+ Log.i(TAG, "Computing dirty sets.");
203+ long ids[] = computeDirtySets(context);
204+ if (ids != null && observer != null) {
205+ observer.onChange(ids);
206+ }
207+ if (ids.length > 0) {
208+ sList = null;
209+ }
210+ Log.i(TAG, "Done computing dirty sets for num " + ids.length);
211+ }
212+ });
213+ } else {
214+ QUEUE_DIRTY_SENSE = true;
215+ }
216+ }
217+
218+ public static final void markDirty(final Context context) {
219+ sList = null;
220+ sAlbumCache.put(ALBUM_CACHE_DIRTY_INDEX, sDummyData);
221+ if (CACHE_THREAD.get() == null) {
222+ QUEUE_DIRTY_SENSE = false;
223+ QUEUE_DIRTY_ALL = false;
224+ QUEUE_DIRTY_SET = false;
225+ restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
226+ public void run() {
227+ refresh(context);
228+ }
229+ });
230+ } else {
231+ QUEUE_DIRTY_ALL = true;
232+ }
233+ }
234+
235+ public static final void markDirtyImmediate(final long id) {
236+ if (id == Shared.INVALID) {
237+ return;
238+ }
239+ sList = null;
240+ byte[] data = longToByteArray(id);
241+ final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
242+ if (existingData != null && existingData.length > 0) {
243+ final long[] ids = toLongArray(existingData);
244+ final int numIds = ids.length;
245+ for (int i = 0; i < numIds; ++i) {
246+ if (ids[i] == id) {
247+ return;
248+ }
249+ }
250+ // Add this to the existing keys and concatenate the byte arrays.
251+ data = concat(data, existingData);
252+ }
253+ sAlbumCache.put(ALBUM_CACHE_DIRTY_BUCKET_INDEX, data);
254+ }
255+
256+ public static final void markDirty(final Context context, final long id) {
257+ markDirtyImmediate(id);
258+ if (CACHE_THREAD.get() == null) {
259+ QUEUE_DIRTY_SET = false;
260+ restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
261+ public void run() {
262+ refreshDirtySets(context);
263+ }
264+ });
265+ } else {
266+ QUEUE_DIRTY_SET = true;
267+ }
268+ }
269+
270+ public static final boolean setHasItems(final ContentResolver cr, final long setId) {
271+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
272+ final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
273+ final StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + "=" + setId);
274+ final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, whereString.toString(), null, null);
275+ if (cursorImages != null && cursorImages.getCount() > 0) {
276+ cursorImages.close();
277+ return true;
278+ }
279+ final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, whereString.toString(), null, null);
280+ if (cursorVideos != null && cursorVideos.getCount() > 0) {
281+ cursorVideos.close();
282+ return true;
283+ }
284+ return false;
285+ }
286+
287+ public static final void loadMediaSets(final MediaFeed feed, final DataSource source, final boolean includeImages,
288+ final boolean includeVideos) {
289+ int timeElapsed = 0;
290+ while (!isCacheReady(true) && timeElapsed < 10000) {
291+ try {
292+ Thread.sleep(300);
293+ } catch (InterruptedException e) {
294+ return;
295+ }
296+ timeElapsed += 300;
297+ }
298+ final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
299+ if (albumData != null && albumData.length > 0) {
300+ final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
301+ try {
302+ final int numAlbums = dis.readInt();
303+ for (int i = 0; i < numAlbums; ++i) {
304+ final long setId = dis.readLong();
305+ final String name = Utils.readUTF(dis);
306+ final boolean hasImages = dis.readBoolean();
307+ final boolean hasVideos = dis.readBoolean();
308+ MediaSet mediaSet = feed.getMediaSet(setId);
309+ if (mediaSet == null) {
310+ mediaSet = feed.addMediaSet(setId, source);
311+ }
312+ if ((includeImages && hasImages) || (includeVideos && hasVideos)) {
313+ mediaSet.mName = name;
314+ mediaSet.mHasImages = hasImages;
315+ mediaSet.mHasVideos = hasVideos;
316+ mediaSet.mPicasaAlbumId = Shared.INVALID;
317+ mediaSet.generateTitle(true);
318+ }
319+ }
320+ } catch (IOException e) {
321+ Log.e(TAG, "Error loading albums.");
322+ sAlbumCache.deleteAll();
323+ putLocaleForAlbumCache(Locale.getDefault());
324+ }
325+ } else {
326+ Log.d(TAG, "No albums found.");
327+ }
328+ }
329+
330+ public static final void loadMediaSet(final MediaFeed feed, final DataSource source, final long bucketId) {
331+ int timeElapsed = 0;
332+ while (!isCacheReady(false) && timeElapsed < 10000) {
333+ try {
334+ Thread.sleep(300);
335+ } catch (InterruptedException e) {
336+ return;
337+ }
338+ timeElapsed += 300;
339+ }
340+ final byte[] albumData = sAlbumCache.get(ALBUM_CACHE_METADATA_INDEX, 0);
341+ if (albumData != null && albumData.length > 0) {
342+ DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
343+ try {
344+ final int numAlbums = dis.readInt();
345+ for (int i = 0; i < numAlbums; ++i) {
346+ final long setId = dis.readLong();
347+ MediaSet mediaSet = null;
348+ if (setId == bucketId) {
349+ mediaSet = feed.getMediaSet(setId);
350+ if (mediaSet == null) {
351+ mediaSet = feed.addMediaSet(setId, source);
352+ }
353+ } else {
354+ mediaSet = new MediaSet();
355+ }
356+ mediaSet.mName = Utils.readUTF(dis);
357+ if (setId == bucketId) {
358+ mediaSet.mPicasaAlbumId = Shared.INVALID;
359+ mediaSet.generateTitle(true);
360+ return;
361+ }
362+ }
363+ } catch (IOException e) {
364+ Log.e(TAG, "Error finding album " + bucketId);
365+ sAlbumCache.deleteAll();
366+ putLocaleForAlbumCache(Locale.getDefault());
367+ }
368+ } else {
369+ Log.d(TAG, "No album found for album id " + bucketId);
370+ }
371+ }
372+
373+ public static final void loadMediaItemsIntoMediaFeed(final MediaFeed feed, final MediaSet set, final int rangeStart,
374+ final int rangeEnd, final boolean includeImages, final boolean includeVideos) {
375+ int timeElapsed = 0;
376+ byte[] albumData = null;
377+ while (!isCacheReady(set.mId) && timeElapsed < 30000) {
378+ try {
379+ Thread.sleep(300);
380+ } catch (InterruptedException e) {
381+ return;
382+ }
383+ timeElapsed += 300;
384+ }
385+ albumData = sAlbumCache.get(set.mId, 0);
386+ if (albumData != null && set.mNumItemsLoaded < set.getNumExpectedItems()) {
387+ final DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(albumData), 256));
388+ try {
389+ final int numItems = dis.readInt();
390+ set.setNumExpectedItems(numItems);
391+ set.mMinTimestamp = dis.readLong();
392+ set.mMaxTimestamp = dis.readLong();
393+ for (int i = 0; i < numItems; ++i) {
394+ final MediaItem item = new MediaItem();
395+ // Must preserve order with method that writes to cache.
396+ item.mId = dis.readLong();
397+ item.mCaption = Utils.readUTF(dis);
398+ item.mMimeType = Utils.readUTF(dis);
399+ item.setMediaType(dis.readInt());
400+ item.mLatitude = dis.readDouble();
401+ item.mLongitude = dis.readDouble();
402+ item.mDateTakenInMs = dis.readLong();
403+ item.mTriedRetrievingExifDateTaken = dis.readBoolean();
404+ item.mDateAddedInSec = dis.readLong();
405+ item.mDateModifiedInSec = dis.readLong();
406+ item.mDurationInSec = dis.readInt();
407+ item.mRotation = (float) dis.readInt();
408+ item.mFilePath = Utils.readUTF(dis);
409+ int itemMediaType = item.getMediaType();
410+ if ((itemMediaType == MediaItem.MEDIA_TYPE_IMAGE && includeImages)
411+ || (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO && includeVideos)) {
412+ String baseUri = (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) ? BASE_CONTENT_STRING_IMAGES
413+ : BASE_CONTENT_STRING_VIDEOS;
414+ item.mContentUri = baseUri + item.mId;
415+ feed.addItemToMediaSet(item, set);
416+ }
417+ }
418+ dis.close();
419+ } catch (IOException e) {
420+ Log.e(TAG, "Error loading items for album " + set.mName);
421+ sAlbumCache.deleteAll();
422+ putLocaleForAlbumCache(Locale.getDefault());
423+ }
424+ } else {
425+ Log.d(TAG, "No items found for album " + set.mName);
426+ }
427+ set.updateNumExpectedItems();
428+ set.generateTitle(true);
429+ }
430+
431+ public static final void populateVideoItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
432+ final String baseUri) {
433+ item.setMediaType(MediaItem.MEDIA_TYPE_VIDEO);
434+ populateMediaItemFromCursor(item, cr, cursor, baseUri);
435+ }
436+
437+ public static final void populateMediaItemFromCursor(final MediaItem item, final ContentResolver cr, final Cursor cursor,
438+ final String baseUri) {
439+ item.mId = cursor.getLong(CacheService.MEDIA_ID_INDEX);
440+ item.mCaption = cursor.getString(CacheService.MEDIA_CAPTION_INDEX);
441+ item.mMimeType = cursor.getString(CacheService.MEDIA_MIME_TYPE_INDEX);
442+ item.mLatitude = cursor.getDouble(CacheService.MEDIA_LATITUDE_INDEX);
443+ item.mLongitude = cursor.getDouble(CacheService.MEDIA_LONGITUDE_INDEX);
444+ item.mDateTakenInMs = cursor.getLong(CacheService.MEDIA_DATE_TAKEN_INDEX);
445+ item.mDateAddedInSec = cursor.getLong(CacheService.MEDIA_DATE_ADDED_INDEX);
446+ item.mDateModifiedInSec = cursor.getLong(CacheService.MEDIA_DATE_MODIFIED_INDEX);
447+ if (item.mDateTakenInMs == item.mDateModifiedInSec) {
448+ item.mDateTakenInMs = item.mDateModifiedInSec * 1000;
449+ }
450+ item.mFilePath = cursor.getString(CacheService.MEDIA_DATA_INDEX);
451+ if (baseUri != null)
452+ item.mContentUri = baseUri + item.mId;
453+ final int itemMediaType = item.getMediaType();
454+ // Check to see if a new date taken is available.
455+ final long dateTaken = fetchDateTaken(item);
456+ if (dateTaken != -1L && item.mContentUri != null) {
457+ item.mDateTakenInMs = dateTaken;
458+ final ContentValues values = new ContentValues();
459+ if (itemMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
460+ values.put(Video.VideoColumns.DATE_TAKEN, item.mDateTakenInMs);
461+ } else {
462+ values.put(Images.ImageColumns.DATE_TAKEN, item.mDateTakenInMs);
463+ }
464+ cr.update(Uri.parse(item.mContentUri), values, null, null);
465+ }
466+
467+ final int orientationDurationValue = cursor.getInt(CacheService.MEDIA_ORIENTATION_OR_DURATION_INDEX);
468+ if (itemMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
469+ item.mRotation = orientationDurationValue;
470+ } else {
471+ item.mDurationInSec = orientationDurationValue;
472+ }
473+ }
474+
475+ // Returns -1 if we failed to examine EXIF information or EXIF parsing
476+ // failed.
477+ public static final long fetchDateTaken(final MediaItem item) {
478+ if (!item.isDateTakenValid() && !item.mTriedRetrievingExifDateTaken
479+ && (item.mFilePath.endsWith(".jpg") || item.mFilePath.endsWith(".jpeg"))) {
480+ try {
481+ Log.i(TAG, "Parsing date taken from exif");
482+ final ExifInterface exif = new ExifInterface(item.mFilePath);
483+ final String dateTakenStr = exif.getAttribute(ExifInterface.TAG_DATETIME);
484+ if (dateTakenStr != null) {
485+ try {
486+ final Date dateTaken = mDateFormat.parse(dateTakenStr);
487+ return dateTaken.getTime();
488+ } catch (ParseException pe) {
489+ try {
490+ final Date dateTaken = mAltDateFormat.parse(dateTakenStr);
491+ return dateTaken.getTime();
492+ } catch (ParseException pe2) {
493+ Log.i(TAG, "Unable to parse date out of string - " + dateTakenStr);
494+ }
495+ }
496+ }
497+ } catch (Exception e) {
498+ Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
499+ }
500+
501+ // Ensures that we only try retrieving EXIF date taken once.
502+ item.mTriedRetrievingExifDateTaken = true;
503+ }
504+ return -1L;
505+ }
506+
507+ public static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
508+ final long timestamp) {
509+ final DiskCache thumbnailCache = (isVideo) ? LocalDataSource.sThumbnailCacheVideo : LocalDataSource.sThumbnailCache;
510+ return queryThumbnail(context, thumbId, origId, isVideo, thumbnailCache, timestamp);
511+ }
512+
513+ public static final ImageList getImageList(final Context context) {
514+ if (sList != null)
515+ return sList;
516+ ImageList list = new ImageList();
517+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
518+ final ContentResolver cr = context.getContentResolver();
519+ final Cursor cursorImages = cr.query(uriImages, THUMBNAIL_PROJECTION, null, null, null);
520+ if (cursorImages != null && cursorImages.moveToFirst()) {
521+ final int size = cursorImages.getCount();
522+ final long[] ids = new long[size];
523+ final long[] thumbnailIds = new long[size];
524+ final long[] timestamp = new long[size];
525+ final int[] orientation = new int[size];
526+ int ctr = 0;
527+ do {
528+ if (Thread.interrupted()) {
529+ break;
530+ }
531+ ids[ctr] = cursorImages.getLong(THUMBNAIL_ID_INDEX);
532+ timestamp[ctr] = cursorImages.getLong(THUMBNAIL_DATE_MODIFIED_INDEX);
533+ thumbnailIds[ctr] = Utils.Crc64Long(cursorImages.getString(THUMBNAIL_DATA_INDEX));
534+ orientation[ctr] = cursorImages.getInt(THUMBNAIL_ORIENTATION_INDEX);
535+ ++ctr;
536+ } while (cursorImages.moveToNext());
537+ cursorImages.close();
538+ list.ids = ids;
539+ list.thumbids = thumbnailIds;
540+ list.timestamp = timestamp;
541+ list.orientation = orientation;
542+ }
543+ if (sList == null) {
544+ sList = list;
545+ }
546+ return list;
547+ }
548+
549+ private static final byte[] queryThumbnail(final Context context, final long thumbId, final long origId, final boolean isVideo,
550+ final DiskCache thumbnailCache, final long timestamp) {
551+ if (!((Gallery) context).isPaused()) {
552+ final Thread thumbnailThread = THUMBNAIL_THREAD.getAndSet(null);
553+ if (thumbnailThread != null) {
554+ thumbnailThread.interrupt();
555+ }
556+ }
557+ byte[] bitmap = thumbnailCache.get(thumbId, timestamp);
558+ if (bitmap == null) {
559+ Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
560+ final long time = SystemClock.uptimeMillis();
561+ bitmap = buildThumbnailForId(context, thumbnailCache, thumbId, origId, isVideo, DEFAULT_THUMBNAIL_WIDTH,
562+ DEFAULT_THUMBNAIL_HEIGHT);
563+ Log.i(TAG, "Built thumbnail and screennail for " + origId + " in " + (SystemClock.uptimeMillis() - time));
564+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
565+ }
566+ return bitmap;
567+ }
568+
569+ private static final void buildThumbnails(final Context context) {
570+ Log.i(TAG, "Preparing DiskCache for all thumbnails.");
571+ ImageList list = getImageList(context);
572+ final int size = (list.ids == null) ? 0 : list.ids.length;
573+ final long[] ids = list.ids;
574+ final long[] timestamp = list.timestamp;
575+ final long[] thumbnailIds = list.thumbids;
576+ final DiskCache thumbnailCache = LocalDataSource.sThumbnailCache;
577+ for (int i = 0; i < size; ++i) {
578+ if (Thread.interrupted()) {
579+ return;
580+ }
581+ final long id = ids[i];
582+ final long timeModifiedInSec = timestamp[i];
583+ final long thumbnailId = thumbnailIds[i];
584+ if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
585+ buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
586+ DEFAULT_THUMBNAIL_HEIGHT);
587+ }
588+ }
589+ Log.i(TAG, "DiskCache ready for all thumbnails.");
590+ }
591+
592+ private static final byte[] buildThumbnailForId(final Context context, final DiskCache thumbnailCache, final long thumbId,
593+ final long origId, final boolean isVideo, final int thumbnailWidth, final int thumbnailHeight) {
594+ if (origId == Shared.INVALID) {
595+ return null;
596+ }
597+ try {
598+ Bitmap bitmap = null;
599+ Thread.sleep(1);
600+ if (!isVideo) {
601+ final String uriString = BASE_CONTENT_STRING_IMAGES + origId;
602+ UriTexture.invalidateCache(thumbId, 1024);
603+ try {
604+ bitmap = UriTexture.createFromUri(context, uriString, 1024, 1024, thumbId, null);
605+ } catch (IOException e) {
606+ return null;
607+ } catch (URISyntaxException e) {
608+ return null;
609+ }
610+ } else {
611+ Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
612+ new Thread() {
613+ public void run() {
614+ try {
615+ Thread.sleep(5000);
616+ } catch (InterruptedException e) {
617+ ;
618+ }
619+ try {
620+ MediaStore.Video.Thumbnails.cancelThumbnailRequest(context.getContentResolver(), origId);
621+ } catch (Exception e) {
622+ ;
623+ }
624+ }
625+ }.start();
626+ bitmap = MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), origId,
627+ MediaStore.Video.Thumbnails.MICRO_KIND, null);
628+ }
629+ if (bitmap == null) {
630+ return null;
631+ }
632+ final byte[] retVal = writeBitmapToCache(thumbnailCache, thumbId, origId, bitmap, thumbnailWidth, thumbnailHeight);
633+ return retVal;
634+ } catch (InterruptedException e) {
635+ return null;
636+ }
637+ }
638+
639+ public static final byte[] writeBitmapToCache(final DiskCache thumbnailCache, final long thumbId, final long origId,
640+ final Bitmap bitmap, final int thumbnailWidth, final int thumbnailHeight) {
641+ final int width = bitmap.getWidth();
642+ final int height = bitmap.getHeight();
643+ // Detect faces to find the focal point, otherwise fall back to the
644+ // image center.
645+ int focusX = width / 2;
646+ int focusY = height / 2;
647+ // We have commented out face detection since it slows down the
648+ // generation of the thumbnail and screennail.
649+
650+ // final FaceDetector faceDetector = new FaceDetector(width, height, 1);
651+ // final FaceDetector.Face[] faces = new FaceDetector.Face[1];
652+ // final int numFaces = faceDetector.findFaces(bitmap, faces);
653+ // if (numFaces > 0 && faces[0].confidence() >=
654+ // FaceDetector.Face.CONFIDENCE_THRESHOLD) {
655+ // final PointF midPoint = new PointF();
656+ // faces[0].getMidPoint(midPoint);
657+ // focusX = (int) midPoint.x;
658+ // focusY = (int) midPoint.y;
659+ // }
660+
661+ // Crop to thumbnail aspect ratio biased towards the focus point.
662+ int cropX;
663+ int cropY;
664+ int cropWidth;
665+ int cropHeight;
666+ float scaleFactor;
667+ if (thumbnailWidth * height < thumbnailHeight * width) {
668+ // Vertically constrained.
669+ cropWidth = thumbnailWidth * height / thumbnailHeight;
670+ cropX = Math.max(0, Math.min(focusX - cropWidth / 2, width - cropWidth));
671+ cropY = 0;
672+ cropHeight = height;
673+ scaleFactor = (float) thumbnailHeight / height;
674+ } else {
675+ // Horizontally constrained.
676+ cropHeight = thumbnailHeight * width / thumbnailWidth;
677+ cropY = Math.max(0, Math.min(focusY - cropHeight / 2, height - cropHeight));
678+ cropX = 0;
679+ cropWidth = width;
680+ scaleFactor = (float) thumbnailWidth / width;
681+ }
682+ final Bitmap finalBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.RGB_565);
683+ final Canvas canvas = new Canvas(finalBitmap);
684+ final Paint paint = new Paint();
685+ paint.setFilterBitmap(true);
686+ canvas.drawColor(0);
687+ canvas.drawBitmap(bitmap, new Rect(cropX, cropY, cropX + cropWidth, cropY + cropHeight), new Rect(0, 0, thumbnailWidth,
688+ thumbnailHeight), paint);
689+ bitmap.recycle();
690+
691+ // Store (long thumbnailId, short focusX, short focusY, JPEG data).
692+ final ByteArrayOutputStream cacheOutput = new ByteArrayOutputStream(16384);
693+ final DataOutputStream dataOutput = new DataOutputStream(cacheOutput);
694+ byte[] retVal = null;
695+ try {
696+ dataOutput.writeLong(origId);
697+ dataOutput.writeShort((int) ((focusX - cropX) * scaleFactor));
698+ dataOutput.writeShort((int) ((focusY - cropY) * scaleFactor));
699+ dataOutput.flush();
700+ finalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, cacheOutput);
701+ retVal = cacheOutput.toByteArray();
702+ synchronized (thumbnailCache) {
703+ thumbnailCache.put(thumbId, retVal);
704+ }
705+ cacheOutput.close();
706+ } catch (Exception e) {
707+ ;
708+ }
709+ return retVal;
710+ }
711+
712+ public CacheService() {
713+ super("CacheService");
714+ }
715+
716+ @Override
717+ protected void onHandleIntent(final Intent intent) {
718+ Log.i(TAG, "Starting CacheService");
719+ if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) {
720+ sAlbumCache.deleteAll();
721+ putLocaleForAlbumCache(Locale.getDefault());
722+ }
723+ Locale locale = getLocaleForAlbumCache();
724+ if (locale != null && locale.equals(Locale.getDefault())) {
725+ // The cache is in the same locale as the system locale.
726+ if (!isCacheReady(false)) {
727+ // The albums and their items have not yet been cached, we need
728+ // to run the service.
729+ startNewCacheThread();
730+ } else {
731+ startNewCacheThreadForDirtySets();
732+ }
733+ } else {
734+ // The locale has changed, we need to regenerate the strings.
735+ sAlbumCache.deleteAll();
736+ putLocaleForAlbumCache(Locale.getDefault());
737+ startNewCacheThread();
738+ }
739+ if (intent.getBooleanExtra("checkthumbnails", false)) {
740+ startNewThumbnailThread(this);
741+ } else {
742+ final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null);
743+ if (existingThread != null) {
744+ existingThread.interrupt();
745+ }
746+ }
747+ }
748+
749+ private static final void putLocaleForAlbumCache(final Locale locale) {
750+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
751+ final DataOutputStream dos = new DataOutputStream(bos);
752+ try {
753+ Utils.writeUTF(dos, locale.getCountry());
754+ Utils.writeUTF(dos, locale.getLanguage());
755+ Utils.writeUTF(dos, locale.getVariant());
756+ dos.flush();
757+ bos.flush();
758+ final byte[] data = bos.toByteArray();
759+ sAlbumCache.put(ALBUM_CACHE_LOCALE_INDEX, data);
760+ sAlbumCache.flush();
761+ dos.close();
762+ bos.close();
763+ } catch (IOException e) {
764+ // Could not write locale to cache.
765+ Log.i(TAG, "Error writing locale to cache.");
766+ ;
767+ }
768+ }
769+
770+ private static final Locale getLocaleForAlbumCache() {
771+ final byte[] data = sAlbumCache.get(ALBUM_CACHE_LOCALE_INDEX, 0);
772+ if (data != null && data.length > 0) {
773+ ByteArrayInputStream bis = new ByteArrayInputStream(data);
774+ DataInputStream dis = new DataInputStream(bis);
775+ try {
776+ String country = Utils.readUTF(dis);
777+ if (country == null)
778+ country = "";
779+ String language = Utils.readUTF(dis);
780+ if (language == null)
781+ language = "";
782+ String variant = Utils.readUTF(dis);
783+ if (variant == null)
784+ variant = "";
785+ final Locale locale = new Locale(language, country, variant);
786+ dis.close();
787+ bis.close();
788+ return locale;
789+ } catch (IOException e) {
790+ // Could not read locale in cache.
791+ Log.i(TAG, "Error reading locale from cache.");
792+ return null;
793+ }
794+ }
795+ return null;
796+ }
797+
798+ private static final void restartThread(final AtomicReference<Thread> threadRef, final String name, final Runnable action) {
799+ // Create a new thread.
800+ final Thread newThread = new Thread() {
801+ public void run() {
802+ try {
803+ action.run();
804+ } finally {
805+ threadRef.compareAndSet(this, null);
806+ }
807+ }
808+ };
809+ newThread.setName(name);
810+ newThread.start();
811+
812+ // Interrupt any existing thread.
813+ final Thread existingThread = threadRef.getAndSet(newThread);
814+ if (existingThread != null) {
815+ existingThread.interrupt();
816+ }
817+ }
818+
819+ public static final void startNewThumbnailThread(final Context context) {
820+ restartThread(THUMBNAIL_THREAD, "ThumbnailRefresh", new Runnable() {
821+ public void run() {
822+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
823+ try {
824+ // It is an optimization to prevent the thumbnailer from
825+ // running while the application loads
826+ Thread.sleep(THUMBNAILER_WAIT_IN_MS);
827+ } catch (InterruptedException e) {
828+ return;
829+ }
830+ CacheService.buildThumbnails(context);
831+ }
832+ });
833+ }
834+
835+ private void startNewCacheThread() {
836+ restartThread(CACHE_THREAD, "CacheRefresh", new Runnable() {
837+ public void run() {
838+ refresh(CacheService.this);
839+ }
840+ });
841+ }
842+
843+ private void startNewCacheThreadForDirtySets() {
844+ restartThread(CACHE_THREAD, "CacheRefreshDirtySets", new Runnable() {
845+ public void run() {
846+ refreshDirtySets(CacheService.this);
847+ }
848+ });
849+ }
850+
851+ private static final byte[] concat(final byte[] A, final byte[] B) {
852+ final byte[] C = (byte[]) new byte[A.length + B.length];
853+ System.arraycopy(A, 0, C, 0, A.length);
854+ System.arraycopy(B, 0, C, A.length, B.length);
855+ return C;
856+ }
857+
858+ private static final long toLong(final byte[] data) {
859+ // 8 bytes for a long
860+ if (data == null || data.length < 8)
861+ return 0;
862+ final ByteBuffer bBuffer = ByteBuffer.wrap(data);
863+ final LongBuffer lBuffer = bBuffer.asLongBuffer();
864+ final int numLongs = lBuffer.capacity();
865+ return lBuffer.get(0);
866+ }
867+
868+ private static final long[] toLongArray(final byte[] data) {
869+ final ByteBuffer bBuffer = ByteBuffer.wrap(data);
870+ final LongBuffer lBuffer = bBuffer.asLongBuffer();
871+ final int numLongs = lBuffer.capacity();
872+ final long[] retVal = new long[numLongs];
873+ for (int i = 0; i < numLongs; ++i) {
874+ retVal[i] = lBuffer.get(i);
875+ }
876+ return retVal;
877+ }
878+
879+ private static final byte[] longToByteArray(final long l) {
880+ final byte[] bArray = new byte[8];
881+ final ByteBuffer bBuffer = ByteBuffer.wrap(bArray);
882+ final LongBuffer lBuffer = bBuffer.asLongBuffer();
883+ lBuffer.put(0, l);
884+ return bArray;
885+ }
886+
887+ private final static void refresh(final Context context) {
888+ // First we build the album cache.
889+ // This is the meta-data about the albums / buckets on the SD card.
890+ Log.i(TAG, "Refreshing cache.");
891+ int priority = Process.getThreadPriority(Process.myTid());
892+ Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE);
893+ sAlbumCache.deleteAll();
894+ putLocaleForAlbumCache(Locale.getDefault());
895+
896+ final ArrayList<MediaSet> sets = new ArrayList<MediaSet>();
897+ LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>();
898+ Log.i(TAG, "Building albums.");
899+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
900+ final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
901+ final ContentResolver cr = context.getContentResolver();
902+
903+ final Cursor cursorImages = cr.query(uriImages, BUCKET_PROJECTION_IMAGES, null, null, DEFAULT_BUCKET_SORT_ORDER);
904+ final Cursor cursorVideos = cr.query(uriVideos, BUCKET_PROJECTION_VIDEOS, null, null, DEFAULT_BUCKET_SORT_ORDER);
905+ Cursor[] cursors = new Cursor[2];
906+ cursors[0] = cursorImages;
907+ cursors[1] = cursorVideos;
908+ final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.BUCKET_DISPLAY_NAME, SortCursor.TYPE_STRING, true);
909+ try {
910+ if (sortCursor != null && sortCursor.moveToFirst()) {
911+ sets.ensureCapacity(sortCursor.getCount());
912+ acceleratedSets = new LongSparseArray<MediaSet>(sortCursor.getCount());
913+ MediaSet cameraSet = new MediaSet();
914+ cameraSet.mId = LocalDataSource.CAMERA_BUCKET_ID;
915+ cameraSet.mName = context.getResources().getString(R.string.camera);
916+ sets.add(cameraSet);
917+ acceleratedSets.put(cameraSet.mId, cameraSet);
918+ do {
919+ if (Thread.interrupted()) {
920+ return;
921+ }
922+ long setId = sortCursor.getLong(BUCKET_ID_INDEX);
923+ MediaSet mediaSet = findSet(setId, acceleratedSets);
924+ if (mediaSet == null) {
925+ mediaSet = new MediaSet();
926+ mediaSet.mId = setId;
927+ mediaSet.mName = sortCursor.getString(BUCKET_NAME_INDEX);
928+ sets.add(mediaSet);
929+ acceleratedSets.put(setId, mediaSet);
930+ }
931+ mediaSet.mHasImages |= (sortCursor.getCurrentCursorIndex() == 0);
932+ mediaSet.mHasVideos |= (sortCursor.getCurrentCursorIndex() == 1);
933+ } while (sortCursor.moveToNext());
934+ sortCursor.close();
935+ }
936+ } finally {
937+ if (sortCursor != null)
938+ sortCursor.close();
939+ }
940+
941+ sAlbumCache.put(ALBUM_CACHE_INCOMPLETE_INDEX, sDummyData);
942+ writeSetsToCache(sets);
943+ Log.i(TAG, "Done building albums.");
944+ // Now we must cache the items contained in every album / bucket.
945+ populateMediaItemsForSets(context, sets, acceleratedSets, false);
946+ sAlbumCache.delete(ALBUM_CACHE_INCOMPLETE_INDEX);
947+ Process.setThreadPriority(priority);
948+
949+ // Complete any queued dirty requests
950+ processQueuedDirty(context);
951+ }
952+
953+ private final static void refreshDirtySets(final Context context) {
954+ final byte[] existingData = sAlbumCache.get(ALBUM_CACHE_DIRTY_BUCKET_INDEX, 0);
955+ if (existingData != null && existingData.length > 0) {
956+ final long[] ids = toLongArray(existingData);
957+ final int numIds = ids.length;
958+ if (numIds > 0) {
959+ final ArrayList<MediaSet> sets = new ArrayList<MediaSet>(numIds);
960+ final LongSparseArray<MediaSet> acceleratedSets = new LongSparseArray<MediaSet>(numIds);
961+ for (int i = 0; i < numIds; ++i) {
962+ final MediaSet set = new MediaSet();
963+ set.mId = ids[i];
964+ sets.add(set);
965+ acceleratedSets.put(set.mId, set);
966+ }
967+ Log.i(TAG, "Refreshing dirty albums");
968+ populateMediaItemsForSets(context, sets, acceleratedSets, true);
969+ }
970+ }
971+ processQueuedDirty(context);
972+ sAlbumCache.delete(ALBUM_CACHE_DIRTY_BUCKET_INDEX);
973+ }
974+
975+ private static final long[] computeDirtySets(final Context context) {
976+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
977+ final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("distinct", "true").build();
978+ final ContentResolver cr = context.getContentResolver();
979+
980+ final Cursor cursorImages = cr.query(uriImages, SENSE_PROJECTION, null, null, null);
981+ final Cursor cursorVideos = cr.query(uriVideos, SENSE_PROJECTION, null, null, null);
982+ Cursor[] cursors = new Cursor[2];
983+ cursors[0] = cursorImages;
984+ cursors[1] = cursorVideos;
985+ final MergeCursor cursor = new MergeCursor(cursors);
986+ long[] retVal = null;
987+ try {
988+ if (cursor.moveToFirst()) {
989+ retVal = new long[cursor.getCount()];
990+ int ctr = 0;
991+ boolean allDirty = false;
992+ do {
993+ long setId = cursor.getLong(0);
994+ retVal[ctr++] = setId;
995+ byte[] data = sMetaAlbumCache.get(setId, 0);
996+ if (data == null) {
997+ // We need to refresh everything.
998+ markDirty(context);
999+ allDirty = true;
1000+ }
1001+ if (!allDirty) {
1002+ long maxAdded = cursor.getLong(1);
1003+ long oldMaxAdded = toLong(data);
1004+ if (maxAdded > oldMaxAdded) {
1005+ markDirty(context, setId);
1006+ }
1007+ }
1008+ } while (cursor.moveToNext());
1009+ }
1010+ } finally {
1011+ cursor.close();
1012+ }
1013+ processQueuedDirty(context);
1014+ return retVal;
1015+ }
1016+
1017+ private static final void processQueuedDirty(final Context context) {
1018+ if (QUEUE_DIRTY_SENSE) {
1019+ QUEUE_DIRTY_SENSE = false;
1020+ QUEUE_DIRTY_ALL = false;
1021+ QUEUE_DIRTY_SET = false;
1022+ computeDirtySets(context);
1023+ } else if (QUEUE_DIRTY_ALL) {
1024+ QUEUE_DIRTY_ALL = false;
1025+ QUEUE_DIRTY_SET = false;
1026+ QUEUE_DIRTY_SENSE = false;
1027+ refresh(context);
1028+ } else if (QUEUE_DIRTY_SET) {
1029+ QUEUE_DIRTY_SET = false;
1030+ // We don't mark QUEUE_DIRTY_SENSE because a set outside the dirty
1031+ // sets might have gotten modified.
1032+ refreshDirtySets(context);
1033+ }
1034+ }
1035+
1036+ private final static void populateMediaItemsForSets(final Context context, final ArrayList<MediaSet> sets,
1037+ final LongSparseArray<MediaSet> acceleratedSets, boolean useWhere) {
1038+ if (sets == null || sets.size() == 0 || Thread.interrupted()) {
1039+ return;
1040+ }
1041+ Log.i(TAG, "Building items.");
1042+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
1043+ final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
1044+ final ContentResolver cr = context.getContentResolver();
1045+
1046+ String whereClause = null;
1047+ if (useWhere) {
1048+ int numSets = sets.size();
1049+ StringBuffer whereString = new StringBuffer(Images.ImageColumns.BUCKET_ID + " in (");
1050+ for (int i = 0; i < numSets; ++i) {
1051+ whereString.append(sets.get(i).mId);
1052+ if (i != numSets - 1) {
1053+ whereString.append(",");
1054+ }
1055+ }
1056+ whereString.append(")");
1057+ whereClause = whereString.toString();
1058+ Log.i(TAG, "Updating dirty albums where " + whereClause);
1059+ }
1060+
1061+ final Cursor cursorImages = cr.query(uriImages, PROJECTION_IMAGES, whereClause, null, DEFAULT_IMAGE_SORT_ORDER);
1062+ final Cursor cursorVideos = cr.query(uriVideos, PROJECTION_VIDEOS, whereClause, null, DEFAULT_VIDEO_SORT_ORDER);
1063+ final Cursor[] cursors = new Cursor[2];
1064+ cursors[0] = cursorImages;
1065+ cursors[1] = cursorVideos;
1066+ final SortCursor sortCursor = new SortCursor(cursors, Images.ImageColumns.DATE_TAKEN, SortCursor.TYPE_NUMERIC, true);
1067+ if (Thread.interrupted()) {
1068+ return;
1069+ }
1070+ try {
1071+ if (sortCursor != null && sortCursor.moveToFirst()) {
1072+ final int count = sortCursor.getCount();
1073+ final int numSets = sets.size();
1074+ final int approximateCountPerSet = count / numSets;
1075+ for (int i = 0; i < numSets; ++i) {
1076+ final MediaSet set = sets.get(i);
1077+ set.getItems().clear();
1078+ set.setNumExpectedItems(approximateCountPerSet);
1079+ }
1080+ do {
1081+ if (Thread.interrupted()) {
1082+ return;
1083+ }
1084+ final MediaItem item = new MediaItem();
1085+ final boolean isVideo = (sortCursor.getCurrentCursorIndex() == 1);
1086+ if (isVideo) {
1087+ populateVideoItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_VIDEOS);
1088+ } else {
1089+ populateMediaItemFromCursor(item, cr, sortCursor, CacheService.BASE_CONTENT_STRING_IMAGES);
1090+ }
1091+ final long setId = sortCursor.getLong(MEDIA_BUCKET_ID_INDEX);
1092+ final MediaSet set = findSet(setId, acceleratedSets);
1093+ if (set != null) {
1094+ set.getItems().add(item);
1095+ }
1096+ } while (sortCursor.moveToNext());
1097+ }
1098+ } finally {
1099+ if (sortCursor != null)
1100+ sortCursor.close();
1101+ }
1102+ if (sets.size() > 0) {
1103+ writeItemsToCache(sets);
1104+ Log.i(TAG, "Done building items.");
1105+ }
1106+ }
1107+
1108+ private static final void writeSetsToCache(final ArrayList<MediaSet> sets) {
1109+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
1110+ final int numSets = sets.size();
1111+ final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
1112+ try {
1113+ dos.writeInt(numSets);
1114+ for (int i = 0; i < numSets; ++i) {
1115+ if (Thread.interrupted()) {
1116+ return;
1117+ }
1118+ final MediaSet set = sets.get(i);
1119+ dos.writeLong(set.mId);
1120+ Utils.writeUTF(dos, set.mName);
1121+ dos.writeBoolean(set.mHasImages);
1122+ dos.writeBoolean(set.mHasVideos);
1123+ }
1124+ dos.flush();
1125+ sAlbumCache.put(ALBUM_CACHE_METADATA_INDEX, bos.toByteArray());
1126+ dos.close();
1127+ if (numSets == 0) {
1128+ sAlbumCache.deleteAll();
1129+ putLocaleForAlbumCache(Locale.getDefault());
1130+ }
1131+ sAlbumCache.flush();
1132+ } catch (IOException e) {
1133+ Log.e(TAG, "Error writing albums to diskcache.");
1134+ sAlbumCache.deleteAll();
1135+ putLocaleForAlbumCache(Locale.getDefault());
1136+ }
1137+ }
1138+
1139+ private static final void writeItemsToCache(final ArrayList<MediaSet> sets) {
1140+ final int numSets = sets.size();
1141+ for (int i = 0; i < numSets; ++i) {
1142+ if (Thread.interrupted()) {
1143+ return;
1144+ }
1145+ writeItemsForASet(sets.get(i));
1146+ }
1147+ writeMetaAlbumCache(sets);
1148+ sAlbumCache.flush();
1149+ }
1150+
1151+ private static final void writeMetaAlbumCache(ArrayList<MediaSet> sets) {
1152+ final int numSets = sets.size();
1153+ for (int i = 0; i < numSets; ++i) {
1154+ final MediaSet set = sets.get(i);
1155+ byte[] data = longToByteArray(set.mMaxAddedTimestamp);
1156+ sMetaAlbumCache.put(set.mId, data);
1157+ }
1158+ sMetaAlbumCache.flush();
1159+ }
1160+
1161+ private static final void writeItemsForASet(final MediaSet set) {
1162+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
1163+ final DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
1164+ try {
1165+ final ArrayList<MediaItem> items = set.getItems();
1166+ final int numItems = items.size();
1167+ dos.writeInt(numItems);
1168+ dos.writeLong(set.mMinTimestamp);
1169+ dos.writeLong(set.mMaxTimestamp);
1170+ for (int i = 0; i < numItems; ++i) {
1171+ MediaItem item = items.get(i);
1172+ if (set.mId == LocalDataSource.CAMERA_BUCKET_ID || set.mId == LocalDataSource.DOWNLOAD_BUCKET_ID) {
1173+ // Reverse the display order for the camera bucket - want
1174+ // the latest first.
1175+ item = items.get(numItems - i - 1);
1176+ }
1177+ dos.writeLong(item.mId);
1178+ Utils.writeUTF(dos, item.mCaption);
1179+ Utils.writeUTF(dos, item.mMimeType);
1180+ dos.writeInt(item.getMediaType());
1181+ dos.writeDouble(item.mLatitude);
1182+ dos.writeDouble(item.mLongitude);
1183+ dos.writeLong(item.mDateTakenInMs);
1184+ dos.writeBoolean(item.mTriedRetrievingExifDateTaken);
1185+ dos.writeLong(item.mDateAddedInSec);
1186+ dos.writeLong(item.mDateModifiedInSec);
1187+ dos.writeInt(item.mDurationInSec);
1188+ dos.writeInt((int) item.mRotation);
1189+ Utils.writeUTF(dos, item.mFilePath);
1190+ }
1191+ dos.flush();
1192+ sAlbumCache.put(set.mId, bos.toByteArray());
1193+ dos.close();
1194+ } catch (IOException e) {
1195+ Log.e(TAG, "Error writing to diskcache for set " + set.mName);
1196+ sAlbumCache.deleteAll();
1197+ putLocaleForAlbumCache(Locale.getDefault());
1198+ }
1199+ }
1200+
1201+ private static final MediaSet findSet(final long id, final LongSparseArray<MediaSet> acceleratedTable) {
1202+ // This is the accelerated lookup table for the MediaSet based on set
1203+ // id.
1204+ return acceleratedTable.get(id);
1205+ }
10781206 }
--- a/src/com/cooliris/media/BackgroundLayer.java
+++ b/src/com/cooliris/media/BackgroundLayer.java
@@ -21,7 +21,7 @@ public class BackgroundLayer extends Layer {
2121 private static final int ADAPTIVE_BACKGROUND_WIDTH = 256;
2222 private static final int ADAPTIVE_BACKGROUND_HEIGHT = 128;
2323
24- BackgroundLayer(GridLayer layer) {
24+ public BackgroundLayer(GridLayer layer) {
2525 mGridLayer = layer;
2626 }
2727
--- a/src/com/cooliris/media/ConcatenatedDataSource.java
+++ b/src/com/cooliris/media/ConcatenatedDataSource.java
@@ -2,6 +2,7 @@ package com.cooliris.media;
22
33 import java.util.ArrayList;
44
5+
56 import android.util.Log;
67
78 public final class ConcatenatedDataSource implements DataSource {
--- a/src/com/cooliris/media/DataSource.java
+++ b/src/com/cooliris/media/DataSource.java
@@ -2,6 +2,7 @@ package com.cooliris.media;
22
33 import java.util.ArrayList;
44
5+
56 public interface DataSource {
67 // Load the sets to be displayed.
78 void loadMediaSets(final MediaFeed feed);
--- a/src/com/cooliris/media/FlatLocalDataSource.java
+++ /dev/null
@@ -1,35 +0,0 @@
1-//package com.cooliris.media;
2-//
3-//// Deprecated class. Need to remove from perforce
4-//
5-//import java.util.ArrayList;
6-//
7-//public class FlatLocalDataSource implements MediaFeed.DataSource {
8-// private final boolean mIncludeImages;
9-// private final boolean mIncludeVideos;
10-//
11-// public FlatLocalDataSource(boolean includeImages, boolean includeVideos) {
12-// mIncludeImages = includeImages;
13-// mIncludeVideos = includeVideos;
14-// }
15-//
16-// public DiskCache getThumbnailCache() {
17-// return LocalDataSource.sThumbnailCache;
18-// }
19-//
20-// public void loadItemsForSet(MediaFeed feed, MediaSet parentSet, int rangeStart, int rangeEnd) {
21-// // TODO Auto-generated method stub
22-//
23-// }
24-//
25-// public void loadMediaSets(MediaFeed feed) {
26-// MediaSet set = feed.addMediaSet(0, this);
27-// set.name = "Local Media";
28-// }
29-//
30-// public boolean performOperation(int operation, ArrayList<MediaBucket> mediaBuckets, Object data) {
31-// // TODO Auto-generated method stub
32-// return false;
33-// }
34-//
35-//}
--- a/src/com/cooliris/media/Gallery.java
+++ b/src/com/cooliris/media/Gallery.java
@@ -2,6 +2,7 @@ package com.cooliris.media;
22
33 import java.io.IOException;
44 import java.net.URISyntaxException;
5+import java.util.HashMap;
56 import java.util.TimeZone;
67
78 import android.app.Activity;
@@ -27,349 +28,365 @@ import com.cooliris.wallpaper.RandomDataSource;
2728 import com.cooliris.wallpaper.Slideshow;
2829
2930 public final class Gallery extends Activity {
30- public static final TimeZone CURRENT_TIME_ZONE = TimeZone.getDefault();
31- public static float PIXEL_DENSITY = 0.0f;
32- public static boolean NEEDS_REFRESH = true;
31+ public static final TimeZone CURRENT_TIME_ZONE = TimeZone.getDefault();
32+ public static float PIXEL_DENSITY = 0.0f;
33+ public static final int CROP_MSG_INTERNAL = 100;
3334
34- private static final String TAG = "Gallery";
35- public static final int CROP_MSG_INTERNAL = 100;
36- private static final int CROP_MSG = 10;
37- private RenderView mRenderView = null;
38- private GridLayer mGridLayer;
39- private final Handler mHandler = new Handler();
40- private ReverseGeocoder mReverseGeocoder;
41- private boolean mPause;
42- private MediaScannerConnection mConnection;
43- private WakeLock mWakeLock;
44- private static final boolean TEST_WALLPAPER = false;
35+ private static final String TAG = "Gallery";
36+ private static final int CROP_MSG = 10;
37+ private RenderView mRenderView = null;
38+ private GridLayer mGridLayer;
39+ private final Handler mHandler = new Handler();
40+ private ReverseGeocoder mReverseGeocoder;
41+ private boolean mPause;
42+ private MediaScannerConnection mConnection;
43+ private WakeLock mWakeLock;
44+ private HashMap<String, Boolean> mAccountsEnabled;
45+ private static final boolean TEST_WALLPAPER = false;
4546
46- @Override
47- public void onCreate(Bundle savedInstanceState) {
48- super.onCreate(savedInstanceState);
49- final boolean imageManagerHasStorage = ImageManager.quickHasStorage();
50- if (TEST_WALLPAPER || (isViewIntent() && getIntent().getData().equals(Images.Media.EXTERNAL_CONTENT_URI))) {
51- if (!imageManagerHasStorage) {
52- Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
53- finish();
54- } else {
55- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
56- mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
57- mWakeLock.acquire();
58- Slideshow slideshow = new Slideshow(this);
59- slideshow.setDataSource(new RandomDataSource());
60- setContentView(slideshow);
61- }
62- return;
63- }
64- boolean isCacheReady = CacheService.isCacheReady(false);
65- CacheService.startCache(this, false);
66- if (PIXEL_DENSITY == 0.0f) {
67- DisplayMetrics metrics = new DisplayMetrics();
68- getWindowManager().getDefaultDisplay().getMetrics(metrics);
69- PIXEL_DENSITY = metrics.density;
70- }
71- mReverseGeocoder = new ReverseGeocoder(this);
72- mRenderView = new RenderView(this);
73- mGridLayer = new GridLayer(this, (int) (96.0f * PIXEL_DENSITY), (int) (72.0f * PIXEL_DENSITY), new GridLayoutInterface(4),
74- mRenderView);
75- mRenderView.setRootLayer(mGridLayer);
76- setContentView(mRenderView);
77- if (!isPickIntent() && !isViewIntent()) {
78- PicasaDataSource picasaDataSource = new PicasaDataSource(this);
79- if (imageManagerHasStorage) {
80- LocalDataSource localDataSource = new LocalDataSource(this);
81- ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
82- mGridLayer.setDataSource(combinedDataSource);
83- } else {
84- mGridLayer.setDataSource(picasaDataSource);
85- }
86- if (!imageManagerHasStorage) {
87- Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
88- } else {
89- if (!isCacheReady) {
90- Toast.makeText(this, getResources().getString(R.string.loading_new), Toast.LENGTH_LONG).show();
91- } else {
92- // Toast.makeText(this, getResources().getString(R.string.initializing), Toast.LENGTH_SHORT).show();
93- }
94- }
95- } else if (!isViewIntent()) {
96- Intent intent = getIntent();
97- if (intent != null) {
98- String type = intent.resolveType(this);
99- boolean includeImages = isImageType(type);
100- boolean includeVideos = isVideoType(type);
101- LocalDataSource localDataSource = new LocalDataSource(this);
102- ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
103- if (includeImages) {
104- PicasaDataSource picasaDataSource = new PicasaDataSource(this);
105- if (imageManagerHasStorage) {
106- ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
107- mGridLayer.setDataSource(combinedDataSource);
108- } else {
109- mGridLayer.setDataSource(picasaDataSource);
110- }
111- } else {
112- mGridLayer.setDataSource(localDataSource);
113- }
114- mGridLayer.setPickIntent(true);
115- if (!imageManagerHasStorage) {
116- Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
117- } else {
118- Toast.makeText(this, getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG).show();
119- }
120- }
121- } else {
122- // View intent for images.
123- Uri uri = getIntent().getData();
124- boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
125- SingleDataSource localDataSource = new SingleDataSource(this, uri.toString(), slideshow);
126- PicasaDataSource picasaDataSource = new PicasaDataSource(this);
127- ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
128- mGridLayer.setDataSource(combinedDataSource);
129- mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
130- if (SingleDataSource.isSingleImageMode(uri.toString())) {
131- mGridLayer.setSingleImage(false);
132- } else if (slideshow) {
133- mGridLayer.setSingleImage(true);
134- mGridLayer.startSlideshow();
135- }
136- // Toast.makeText(this, getResources().getString(R.string.initializing), Toast.LENGTH_SHORT).show();
137- }
138- Log.i(TAG, "onCreate");
139- }
47+ @Override
48+ public void onCreate(Bundle savedInstanceState) {
49+ super.onCreate(savedInstanceState);
50+ final boolean imageManagerHasStorage = ImageManager.quickHasStorage();
51+ if (TEST_WALLPAPER || (isViewIntent() && getIntent().getData().equals(Images.Media.EXTERNAL_CONTENT_URI))) {
52+ if (!imageManagerHasStorage) {
53+ Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
54+ finish();
55+ } else {
56+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
57+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow.All");
58+ mWakeLock.acquire();
59+ Slideshow slideshow = new Slideshow(this);
60+ slideshow.setDataSource(new RandomDataSource());
61+ setContentView(slideshow);
62+ }
63+ return;
64+ }
65+ final boolean isCacheReady = CacheService.isCacheReady(false);
66+ CacheService.startCache(this, false);
67+ if (PIXEL_DENSITY == 0.0f) {
68+ DisplayMetrics metrics = new DisplayMetrics();
69+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
70+ PIXEL_DENSITY = metrics.density;
71+ }
72+ mReverseGeocoder = new ReverseGeocoder(this);
73+ mRenderView = new RenderView(this);
74+ mGridLayer = new GridLayer(this, (int) (96.0f * PIXEL_DENSITY), (int) (72.0f * PIXEL_DENSITY), new GridLayoutInterface(4),
75+ mRenderView);
76+ mRenderView.setRootLayer(mGridLayer);
77+ setContentView(mRenderView);
78+
79+ // Creating the DataSource objects
80+ final PicasaDataSource picasaDataSource = new PicasaDataSource(this);
81+ final LocalDataSource localDataSource = new LocalDataSource(this);
82+ final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource(localDataSource, picasaDataSource);
83+
84+ // Depending upon the intent, we assign the right dataSource.
85+ if (!isPickIntent() && !isViewIntent()) {
86+ if (imageManagerHasStorage) {
87+ mGridLayer.setDataSource(combinedDataSource);
88+ } else {
89+ mGridLayer.setDataSource(picasaDataSource);
90+ }
91+ if (!imageManagerHasStorage) {
92+ Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
93+ } else if (!isCacheReady) {
94+ Toast.makeText(this, getResources().getString(R.string.loading_new), Toast.LENGTH_LONG).show();
95+ }
96+ } else if (!isViewIntent()) {
97+ final Intent intent = getIntent();
98+ if (intent != null) {
99+ final String type = intent.resolveType(this);
100+ boolean includeImages = isImageType(type);
101+ boolean includeVideos = isVideoType(type);
102+ ((LocalDataSource) localDataSource).setMimeFilter(!includeImages, !includeVideos);
103+ if (includeImages) {
104+ if (imageManagerHasStorage) {
105+ mGridLayer.setDataSource(combinedDataSource);
106+ } else {
107+ mGridLayer.setDataSource(picasaDataSource);
108+ }
109+ } else {
110+ mGridLayer.setDataSource(localDataSource);
111+ }
112+ mGridLayer.setPickIntent(true);
113+ if (!imageManagerHasStorage) {
114+ Toast.makeText(this, getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG).show();
115+ } else {
116+ Toast.makeText(this, getResources().getString(R.string.pick_prompt), Toast.LENGTH_LONG).show();
117+ }
118+ }
119+ } else {
120+ // View intent for images.
121+ Uri uri = getIntent().getData();
122+ boolean slideshow = getIntent().getBooleanExtra("slideshow", false);
123+ final SingleDataSource singleDataSource = new SingleDataSource(this, uri.toString(), slideshow);
124+ final ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource(singleDataSource, picasaDataSource);
125+ mGridLayer.setDataSource(singleCombinedDataSource);
126+ mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri(uri));
127+ if (SingleDataSource.isSingleImageMode(uri.toString())) {
128+ mGridLayer.setSingleImage(false);
129+ } else if (slideshow) {
130+ mGridLayer.setSingleImage(true);
131+ mGridLayer.startSlideshow();
132+ }
133+ }
134+ // We record the set of enabled accounts for picasa.
135+ mAccountsEnabled = PicasaDataSource.getAccountStatus(this);
136+ Log.i(TAG, "onCreate");
137+ }
140138
141- public ReverseGeocoder getReverseGeocoder() {
142- return mReverseGeocoder;
143- }
139+ public ReverseGeocoder getReverseGeocoder() {
140+ return mReverseGeocoder;
141+ }
144142
145- public Handler getHandler() {
146- return mHandler;
147- }
143+ public Handler getHandler() {
144+ return mHandler;
145+ }
148146
149- @Override
150- public void onRestart() {
151- super.onRestart();
152- }
147+ @Override
148+ public void onRestart() {
149+ super.onRestart();
150+ }
153151
154- @Override
155- public void onStart() {
156- super.onStart();
157- }
152+ @Override
153+ public void onStart() {
154+ super.onStart();
155+ }
158156
159- @Override
160- public void onResume() {
161- super.onResume();
162- if (mRenderView != null)
163- mRenderView.onResume();
164- if (NEEDS_REFRESH) {
165- NEEDS_REFRESH = false;
166- CacheService.markDirtyImmediate(LocalDataSource.CAMERA_BUCKET_ID);
167- CacheService.markDirtyImmediate(LocalDataSource.DOWNLOAD_BUCKET_ID);
168- CacheService.startCache(this, false);
169- }
170- mPause = false;
171- }
157+ @Override
158+ public void onResume() {
159+ super.onResume();
160+ if (mRenderView != null)
161+ mRenderView.onResume();
162+ if (mPause) {
163+ // We check to see if the authenticated accounts have changed, and
164+ // if so, reload the datasource.
165+ HashMap<String, Boolean> accountsEnabled = PicasaDataSource.getAccountStatus(this);
166+ String[] keys = new String[accountsEnabled.size()];
167+ keys = accountsEnabled.keySet().toArray(keys);
168+ int numKeys = keys.length;
169+ for (int i = 0; i < numKeys; ++i) {
170+ String key = keys[i];
171+ boolean newValue = accountsEnabled.get(key).booleanValue();
172+ boolean oldValue = false;
173+ Boolean oldValObj = mAccountsEnabled.get(key);
174+ if (oldValObj != null) {
175+ oldValue = oldValObj.booleanValue();
176+ }
177+ if (oldValue != newValue) {
178+ // Reload the datasource.
179+ mGridLayer.setDataSource(mGridLayer.getDataSource());
180+ break;
181+ }
182+ }
183+ mAccountsEnabled = accountsEnabled;
184+ mPause = false;
185+ }
186+ }
172187
173- @Override
174- public void onPause() {
175- super.onPause();
176- if (mRenderView != null)
177- mRenderView.onPause();
178- mPause = true;
179- }
188+ @Override
189+ public void onPause() {
190+ super.onPause();
191+ if (mRenderView != null)
192+ mRenderView.onPause();
193+ mPause = true;
194+ }
180195
181- public boolean isPaused() {
182- return mPause;
183- }
196+ public boolean isPaused() {
197+ return mPause;
198+ }
184199
185- @Override
186- public void onStop() {
187- super.onStop();
188- if (mGridLayer != null)
189- mGridLayer.stop();
190- if (mReverseGeocoder != null) {
191- mReverseGeocoder.flushCache();
192- }
193- LocalDataSource.sThumbnailCache.flush();
194- LocalDataSource.sThumbnailCacheVideo.flush();
195- PicasaDataSource.sThumbnailCache.flush();
196- CacheService.startCache(this, true);
197- }
200+ @Override
201+ public void onStop() {
202+ super.onStop();
203+ if (mGridLayer != null)
204+ mGridLayer.stop();
205+ if (mReverseGeocoder != null) {
206+ mReverseGeocoder.flushCache();
207+ }
208+ LocalDataSource.sThumbnailCache.flush();
209+ LocalDataSource.sThumbnailCacheVideo.flush();
210+ PicasaDataSource.sThumbnailCache.flush();
211+ CacheService.startCache(this, true);
212+ }
198213
199- @Override
200- public void onDestroy() {
201- // Force GLThread to exit.
202- setContentView(R.layout.main);
203- if (mGridLayer != null) {
204- DataSource dataSource = mGridLayer.getDataSource();
205- if (dataSource != null) {
206- dataSource.shutdown();
207- }
208- mGridLayer.shutdown();
209- }
210- if (mWakeLock != null) {
211- if (mWakeLock.isHeld()) {
212- mWakeLock.release();
213- }
214- mWakeLock = null;
215- }
216- if (mReverseGeocoder != null)
217- mReverseGeocoder.shutdown();
218- if (mRenderView != null) {
219- mRenderView.shutdown();
220- mRenderView = null;
221- }
222- mGridLayer = null;
223- super.onDestroy();
224- Log.i(TAG, "onDestroy");
225- }
214+ @Override
215+ public void onDestroy() {
216+ // Force GLThread to exit.
217+ setContentView(R.layout.main);
218+ if (mGridLayer != null) {
219+ DataSource dataSource = mGridLayer.getDataSource();
220+ if (dataSource != null) {
221+ dataSource.shutdown();
222+ }
223+ mGridLayer.shutdown();
224+ }
225+ if (mWakeLock != null) {
226+ if (mWakeLock.isHeld()) {
227+ mWakeLock.release();
228+ }
229+ mWakeLock = null;
230+ }
231+ if (mReverseGeocoder != null)
232+ mReverseGeocoder.shutdown();
233+ if (mRenderView != null) {
234+ mRenderView.shutdown();
235+ mRenderView = null;
236+ }
237+ mGridLayer = null;
238+ super.onDestroy();
239+ Log.i(TAG, "onDestroy");
240+ }
226241
227- @Override
228- public void onConfigurationChanged(Configuration newConfig) {
229- super.onConfigurationChanged(newConfig);
230- if (mGridLayer != null) {
231- mGridLayer.markDirty(30);
232- }
233- if (mRenderView != null)
234- mRenderView.requestRender();
235- Log.i(TAG, "onConfigurationChanged");
236- }
242+ @Override
243+ public void onConfigurationChanged(Configuration newConfig) {
244+ super.onConfigurationChanged(newConfig);
245+ if (mGridLayer != null) {
246+ mGridLayer.markDirty(30);
247+ }
248+ if (mRenderView != null)
249+ mRenderView.requestRender();
250+ Log.i(TAG, "onConfigurationChanged");
251+ }
237252
238- @Override
239- public boolean onKeyDown(int keyCode, KeyEvent event) {
240- if (mRenderView != null) {
241- return mRenderView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
242- } else {
243- return super.onKeyDown(keyCode, event);
244- }
245- }
253+ @Override
254+ public boolean onKeyDown(int keyCode, KeyEvent event) {
255+ if (mRenderView != null) {
256+ return mRenderView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
257+ } else {
258+ return super.onKeyDown(keyCode, event);
259+ }
260+ }
246261
247- private boolean isPickIntent() {
248- String action = getIntent().getAction();
249- return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
250- }
262+ private boolean isPickIntent() {
263+ String action = getIntent().getAction();
264+ return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action));
265+ }
251266
252- private boolean isViewIntent() {
253- String action = getIntent().getAction();
254- return Intent.ACTION_VIEW.equals(action);
255- }
267+ private boolean isViewIntent() {
268+ String action = getIntent().getAction();
269+ return Intent.ACTION_VIEW.equals(action);
270+ }
256271
257- private boolean isImageType(String type) {
258- return type.equals("vnd.android.cursor.dir/image") || type.equals("image/*");
259- }
272+ private boolean isImageType(String type) {
273+ return type.equals("vnd.android.cursor.dir/image") || type.equals("image/*");
274+ }
260275
261- private boolean isVideoType(String type) {
262- return type.equals("vnd.android.cursor.dir/video") || type.equals("video/*");
263- }
276+ private boolean isVideoType(String type) {
277+ return type.equals("vnd.android.cursor.dir/video") || type.equals("video/*");
278+ }
264279
265- @Override
266- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
267- switch (requestCode) {
268- case CROP_MSG: {
269- if (resultCode == RESULT_OK) {
270- setResult(resultCode, data);
271- finish();
272- }
273- break;
274- }
275- case CROP_MSG_INTERNAL: {
276- // We cropped an image, we must try to set the focus of the camera to that image.
277- if (resultCode == RESULT_OK) {
278- String contentUri = data.getAction();
279- if (mGridLayer != null) {
280- mGridLayer.focusItem(contentUri);
281- }
282- }
283- break;
284- }
285- }
286- }
280+ @Override
281+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
282+ switch (requestCode) {
283+ case CROP_MSG: {
284+ if (resultCode == RESULT_OK) {
285+ setResult(resultCode, data);
286+ finish();
287+ }
288+ break;
289+ }
290+ case CROP_MSG_INTERNAL: {
291+ // We cropped an image, we must try to set the focus of the camera
292+ // to that image.
293+ if (resultCode == RESULT_OK) {
294+ String contentUri = data.getAction();
295+ if (mGridLayer != null) {
296+ mGridLayer.focusItem(contentUri);
297+ }
298+ }
299+ break;
300+ }
301+ }
302+ }
287303
288- @Override
289- public void onLowMemory() {
290- if (mRenderView != null) {
291- mRenderView.handleLowMemory();
292- }
293- }
304+ @Override
305+ public void onLowMemory() {
306+ if (mRenderView != null) {
307+ mRenderView.handleLowMemory();
308+ }
309+ }
294310
295- public void launchCropperOrFinish(final MediaItem item) {
296- final Bundle myExtras = getIntent().getExtras();
297- String cropValue = myExtras != null ? myExtras.getString("crop") : null;
298- final String contentUri = item.mContentUri;
299- if (cropValue != null) {
300- Bundle newExtras = new Bundle();
301- if (cropValue.equals("circle")) {
302- newExtras.putString("circleCrop", "true");
303- }
304- Intent cropIntent = new Intent();
305- cropIntent.setData(Uri.parse(contentUri));
306- cropIntent.setClass(this, CropImage.class);
307- cropIntent.putExtras(newExtras);
308- // Pass through any extras that were passed in.
309- cropIntent.putExtras(myExtras);
310- startActivityForResult(cropIntent, CROP_MSG);
311- } else {
312- if (contentUri.startsWith("http://")) {
313- // This is a http uri, we must save it locally first and generate a content uri from it.
314- final ProgressDialog dialog = ProgressDialog.show(this, this.getResources().getString(R.string.initializing),
315- getResources().getString(R.string.running_face_detection), true, false);
316- if (contentUri != null) {
317- MediaScannerConnection.MediaScannerConnectionClient client = new MediaScannerConnection.MediaScannerConnectionClient() {
318- public void onMediaScannerConnected() {
319- if (mConnection != null) {
320- try {
321- final String path = UriTexture.writeHttpDataInDirectory(Gallery.this, contentUri,
322- LocalDataSource.DOWNLOAD_BUCKET_NAME);
323- if (path != null) {
324- mConnection.scanFile(path, item.mMimeType);
325- } else {
326- shutdown("");
327- }
328- } catch (Exception e) {
329- shutdown("");
330- }
331- }
332- }
311+ public void launchCropperOrFinish(final MediaItem item) {
312+ final Bundle myExtras = getIntent().getExtras();
313+ String cropValue = myExtras != null ? myExtras.getString("crop") : null;
314+ final String contentUri = item.mContentUri;
315+ if (cropValue != null) {
316+ Bundle newExtras = new Bundle();
317+ if (cropValue.equals("circle")) {
318+ newExtras.putString("circleCrop", "true");
319+ }
320+ Intent cropIntent = new Intent();
321+ cropIntent.setData(Uri.parse(contentUri));
322+ cropIntent.setClass(this, CropImage.class);
323+ cropIntent.putExtras(newExtras);
324+ // Pass through any extras that were passed in.
325+ cropIntent.putExtras(myExtras);
326+ startActivityForResult(cropIntent, CROP_MSG);
327+ } else {
328+ if (contentUri.startsWith("http://")) {
329+ // This is a http uri, we must save it locally first and
330+ // generate a content uri from it.
331+ final ProgressDialog dialog = ProgressDialog.show(this, this.getResources().getString(R.string.initializing),
332+ getResources().getString(R.string.running_face_detection), true, false);
333+ if (contentUri != null) {
334+ MediaScannerConnection.MediaScannerConnectionClient client = new MediaScannerConnection.MediaScannerConnectionClient() {
335+ public void onMediaScannerConnected() {
336+ if (mConnection != null) {
337+ try {
338+ final String path = UriTexture.writeHttpDataInDirectory(Gallery.this, contentUri,
339+ LocalDataSource.DOWNLOAD_BUCKET_NAME);
340+ if (path != null) {
341+ mConnection.scanFile(path, item.mMimeType);
342+ } else {
343+ shutdown("");
344+ }
345+ } catch (Exception e) {
346+ shutdown("");
347+ }
348+ }
349+ }
333350
334- public void onScanCompleted(String path, Uri uri) {
335- shutdown(uri.toString());
336- }
351+ public void onScanCompleted(String path, Uri uri) {
352+ shutdown(uri.toString());
353+ }
337354
338- public void shutdown(String uri) {
339- dialog.dismiss();
340- performReturn(myExtras, uri.toString());
341- if (mConnection != null) {
342- mConnection.disconnect();
343- }
344- }
345- };
346- MediaScannerConnection connection = new MediaScannerConnection(Gallery.this, client);
347- connection.connect();
348- mConnection = connection;
349- }
350- } else {
351- performReturn(myExtras, contentUri);
352- }
353- }
354- }
355+ public void shutdown(String uri) {
356+ dialog.dismiss();
357+ performReturn(myExtras, uri.toString());
358+ if (mConnection != null) {
359+ mConnection.disconnect();
360+ }
361+ }
362+ };
363+ MediaScannerConnection connection = new MediaScannerConnection(Gallery.this, client);
364+ connection.connect();
365+ mConnection = connection;
366+ }
367+ } else {
368+ performReturn(myExtras, contentUri);
369+ }
370+ }
371+ }
355372
356- private void performReturn(Bundle myExtras, String contentUri) {
357- Intent result = new Intent(null, Uri.parse(contentUri));
358- if (myExtras != null && myExtras.getBoolean("return-data")) {
359- // The size of a transaction should be below 100K.
360- Bitmap bitmap = null;
361- try {
362- bitmap = UriTexture.createFromUri(this, contentUri, 1024, 1024, 0, null);
363- } catch (IOException e) {
364- ;
365- } catch (URISyntaxException e) {
366- ;
367- }
368- if (bitmap != null) {
369- result.putExtra("data", bitmap);
370- }
371- }
372- setResult(RESULT_OK, result);
373- finish();
374- }
373+ private void performReturn(Bundle myExtras, String contentUri) {
374+ Intent result = new Intent(null, Uri.parse(contentUri));
375+ if (myExtras != null && myExtras.getBoolean("return-data")) {
376+ // The size of a transaction should be below 100K.
377+ Bitmap bitmap = null;
378+ try {
379+ bitmap = UriTexture.createFromUri(this, contentUri, 1024, 1024, 0, null);
380+ } catch (IOException e) {
381+ ;
382+ } catch (URISyntaxException e) {
383+ ;
384+ }
385+ if (bitmap != null) {
386+ result.putExtra("data", bitmap);
387+ }
388+ }
389+ setResult(RESULT_OK, result);
390+ finish();
391+ }
375392 }
--- a/src/com/cooliris/media/GridCameraManager.java
+++ b/src/com/cooliris/media/GridCameraManager.java
@@ -1,5 +1,6 @@
11 package com.cooliris.media;
22
3+
34 public final class GridCameraManager {
45 private final GridCamera mCamera;
56 private static final Pool<Vector3f> sPool;
--- a/src/com/cooliris/media/GridInputProcessor.java
+++ b/src/com/cooliris/media/GridInputProcessor.java
@@ -182,6 +182,9 @@ public final class GridInputProcessor implements GestureDetector.OnGestureListen
182182 layer.setZoomValue(1.0f);
183183 }
184184 if (keyCode == KeyEvent.KEYCODE_MENU) {
185+ if (mLayer.getFeed() != null && mLayer.getFeed().isSingleImageMode()) {
186+ return true;
187+ }
185188 if (layer.getHud().getMode() == HudLayer.MODE_NORMAL)
186189 layer.enterSelectionMode();
187190 else
--- a/src/com/cooliris/media/GridLayer.java
+++ b/src/com/cooliris/media/GridLayer.java
@@ -7,1426 +7,1437 @@ import android.hardware.SensorEvent;
77 import android.opengl.GLU;
88 import android.os.PowerManager;
99 import android.os.PowerManager.WakeLock;
10+import android.util.Log;
1011 import android.view.KeyEvent;
1112 import android.view.MotionEvent;
1213 import android.content.Context;
1314
1415 public final class GridLayer extends RootLayer implements MediaFeed.Listener, TimeBar.Listener {
15- public static final int STATE_MEDIA_SETS = 0;
16- public static final int STATE_GRID_VIEW = 1;
17- public static final int STATE_FULL_SCREEN = 2;
18- public static final int STATE_TIMELINE = 3;
19-
20- public static final int ANCHOR_LEFT = 0;
21- public static final int ANCHOR_RIGHT = 1;
22- public static final int ANCHOR_CENTER = 2;
23-
24- public static final int MAX_ITEMS_PER_SLOT = 12;
25- public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
26- public static final int MAX_DISPLAY_SLOTS = 96;
27- public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;
28-
29- private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
30-
31- private static HudLayer sHud;
32- private int mState;
33- private static final IndexRange sBufferedVisibleRange = new IndexRange();
34- private static final IndexRange sVisibleRange = new IndexRange();
35- private static final IndexRange sPreviousDataRange = new IndexRange();
36- private static final IndexRange sCompleteRange = new IndexRange();
37-
38- private static final Pool<Vector3f> sTempVec;
39- private static final Pool<Vector3f> sTempVecAlt;
40- static {
41- Vector3f[] vectorPool = new Vector3f[128];
42- int length = vectorPool.length;
43- for (int i = 0; i < length; ++i) {
44- vectorPool[i] = new Vector3f();
45- }
46- Vector3f[] vectorPoolRenderThread = new Vector3f[128];
47- length = vectorPoolRenderThread.length;
48- for (int i = 0; i < length; ++i) {
49- vectorPoolRenderThread[i] = new Vector3f();
50- }
51- sTempVec = new Pool<Vector3f>(vectorPool);
52- sTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
53- }
54-
55- private static final ArrayList<MediaItem> sTempList = new ArrayList<MediaItem>();
56- private static final MediaItem[] sTempHash = new MediaItem[64];
57-
58- private static final Vector3f sDeltaAnchorPositionUncommited = new Vector3f();
59- private static Vector3f sDeltaAnchorPosition = new Vector3f();
60-
61- // The display primitives.
62- private GridDrawables mDrawables;
63- private float mSelectedAlpha = 0.0f;
64- private float mTargetAlpha = 0.0f;
65-
66- private GridCamera mCamera;
67- private GridCameraManager mCameraManager;
68- private GridDrawManager mDrawManager;
69- private GridInputProcessor mInputProcessor;
70-
71- private boolean mFeedAboutToChange;
72- private boolean mPerformingLayoutChange;
73- private boolean mFeedChanged;
74-
75- private final LayoutInterface mLayoutInterface;
76- private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);
77-
78- private MediaFeed mMediaFeed;
79- private boolean mInAlbum = false;
80- private int mCurrentExpandedSlot;
81-
82- private static final DisplayList sDisplayList = new DisplayList();
83- private static final DisplayItem[] sDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
84- private static final DisplaySlot[] sDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
85- private static ArrayList<MediaItem> sVisibleItems;
86-
87- private float mTimeElapsedSinceTransition;
88- private BackgroundLayer mBackground;
89- private boolean mLocationFilter;
90- private float mZoomValue = 1.0f;
91- private float mCurrentFocusItemWidth = 1.0f;
92- private float mCurrentFocusItemHeight = 1.0f;
93- private float mTimeElapsedSinceGridViewReady = 0.0f;
94-
95- private boolean mSlideshowMode;
96- private boolean mNoDeleteMode = false;
97- private float mTimeElapsedSinceView;
98- private static final MediaBucketList sBucketList = new MediaBucketList();
99- private float mTimeElapsedSinceStackViewReady;
100-
101- private Context mContext;
102- private RenderView mView;
103- private boolean mPickIntent;
104- private boolean mViewIntent;
105- private WakeLock mWakeLock;
106- private int mStartMemoryRange;
107- private int mFramesDirty;
108- private String mRequestFocusContentUri;
109- private int mFrameCount;
110-
111- public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
112- mBackground = new BackgroundLayer(this);
113- mContext = context;
114- mView = view;
115-
116- DisplaySlot[] displaySlots = sDisplaySlots;
117- for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
118- DisplaySlot slot = new DisplaySlot();
119- displaySlots[i] = slot;
120- }
121- mLayoutInterface = layoutInterface;
122- mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
123- mDrawables = new GridDrawables(itemWidth, itemHeight);
124- sBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
125- sVisibleRange.set(Shared.INVALID, Shared.INVALID);
126- sCompleteRange.set(Shared.INVALID, Shared.INVALID);
127- sPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
128- sDeltaAnchorPosition.set(0, 0, 0);
129- sDeltaAnchorPositionUncommited.set(0, 0, 0);
130- sBucketList.clear();
131-
132- sVisibleItems = new ArrayList<MediaItem>();
133- if (sHud == null) {
134- sHud = new HudLayer(context);
135- }
136- sHud.setContext(context);
137- sHud.setGridLayer(this);
138- sHud.getPathBar().clear();
139- sHud.setGridLayer(this);
140- sHud.getTimeBar().setListener(this);
141- sHud.getPathBar().pushLabel(R.drawable.icon_home_small, context.getResources().getString(R.string.app_name),
142- new Runnable() {
143- public void run() {
144- if (sHud.getAlpha() == 1.0f) {
145- if (!mFeedAboutToChange) {
146- setState(STATE_MEDIA_SETS);
147- }
148- } else {
149- sHud.setAlpha(1.0f);
150- }
151- }
152- });
153- mCameraManager = new GridCameraManager(mCamera);
154- mDrawManager = new GridDrawManager(context, mCamera, mDrawables, sDisplayList, sDisplayItems, sDisplaySlots);
155- mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, sTempVec, sDisplayItems);
156- setState(STATE_MEDIA_SETS);
157- }
158-
159- public HudLayer getHud() {
160- return sHud;
161- }
162-
163- public void shutdown() {
164- if (mMediaFeed != null) {
165- mMediaFeed.shutdown();
166- }
167- mContext = null;
168- mBackground = null;
169- sBucketList.clear();
170- mCameraManager = null;
171- mDrawManager = null;
172- mView = null;
173- }
174-
175- public void stop() {
176- endSlideshow();
177- mBackground.clear();
178- handleLowMemory();
179- }
180-
181- @Override
182- public void generate(RenderView view, RenderView.Lists lists) {
183- lists.updateList.add(this);
184- lists.opaqueList.add(this);
185- mBackground.generate(view, lists);
186- lists.blendedList.add(this);
187- lists.hitTestList.add(this);
188- sHud.generate(view, lists);
189- }
190-
191- @Override
192- protected void onSizeChanged() {
193- sHud.setSize(mWidth, mHeight);
194- sHud.setAlpha(1.0f);
195- mBackground.setSize(mWidth, mHeight);
196- mTimeElapsedSinceTransition = 0.0f;
197- if (mView != null) {
198- mView.requestRender();
199- }
200- }
201-
202- public int getState() {
203- return mState;
204- }
205-
206- public void setState(int state) {
207- boolean feedUnchanged = false;
208- if (mState == state) {
209- feedUnchanged = true;
210- }
211- GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
212- GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
213- oldLayout.mNumRows = layoutInterface.mNumRows;
214- oldLayout.mSpacingX = layoutInterface.mSpacingX;
215- oldLayout.mSpacingY = layoutInterface.mSpacingY;
216- GridCamera camera = mCamera;
217- int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
218- MediaFeed feed = mMediaFeed;
219- boolean performLayout = true;
220- mZoomValue = 1.0f;
221- float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
222- if (yStretch < 1.0f) {
223- yStretch = 1.0f;
224- }
225- switch (state) {
226- case STATE_GRID_VIEW:
227- mTimeElapsedSinceGridViewReady = 0.0f;
228- if (feed != null && feedUnchanged == false) {
229- boolean updatedData = feed.restorePreviousClusteringState();
230- if (updatedData) {
231- performLayout = false;
232- }
233- }
234- layoutInterface.mNumRows = numMaxRows;
235- layoutInterface.mSpacingX = (int) (10 * Gallery.PIXEL_DENSITY);
236- layoutInterface.mSpacingY = (int) (10 * Gallery.PIXEL_DENSITY);
237- if (mState == STATE_MEDIA_SETS) {
238- // Entering album.
239- mInAlbum = true;
240- MediaSet set = feed.getCurrentSet();
241- int icon = mDrawables.getIconForSet(set, true);
242- if (set != null) {
243- sHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
244- public void run() {
245- if (mFeedAboutToChange) {
246- return;
247- }
248- if (sHud.getAlpha() == 1.0f) {
249- disableLocationFiltering();
250- mInputProcessor.clearSelection();
251- setState(STATE_GRID_VIEW);
252- } else {
253- sHud.setAlpha(1.0f);
254- }
255- }
256- });
257- }
258- }
259- if (mState == STATE_FULL_SCREEN) {
260- sHud.getPathBar().popLabel();
261- }
262- break;
263- case STATE_TIMELINE:
264- mTimeElapsedSinceStackViewReady = 0.0f;
265- if (feed != null && feedUnchanged == false) {
266- feed.performClustering();
267- performLayout = false;
268- }
269- disableLocationFiltering();
270- layoutInterface.mNumRows = numMaxRows - 1;
271- layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
272- layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
273- break;
274- case STATE_FULL_SCREEN:
275- layoutInterface.mNumRows = 1;
276- layoutInterface.mSpacingX = (int) (40 * Gallery.PIXEL_DENSITY);
277- layoutInterface.mSpacingY = (int) (40 * Gallery.PIXEL_DENSITY);
278- if (mState != STATE_FULL_SCREEN) {
279- sHud.getPathBar().pushLabel(R.drawable.ic_fs_details, "", new Runnable() {
280- public void run() {
281- if (sHud.getAlpha() == 1.0f) {
282- sHud.swapFullscreenLabel();
283- }
284- sHud.setAlpha(1.0f);
285- }
286- });
287- }
288- break;
289- case STATE_MEDIA_SETS:
290- mTimeElapsedSinceStackViewReady = 0.0f;
291- if (feed != null && feedUnchanged == false) {
292- feed.restorePreviousClusteringState();
293- feed.expandMediaSet(Shared.INVALID);
294- performLayout = false;
295- }
296- disableLocationFiltering();
297- mInputProcessor.clearSelection();
298- layoutInterface.mNumRows = numMaxRows - 1;
299- layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
300- layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
301- if (mInAlbum) {
302- if (mState == STATE_FULL_SCREEN) {
303- sHud.getPathBar().popLabel();
304- }
305- sHud.getPathBar().popLabel();
306- mInAlbum = false;
307- }
308- break;
309- }
310- mState = state;
311- sHud.onGridStateChanged();
312- if (performLayout && mFeedAboutToChange == false) {
313- onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
314- }
315- if (state != STATE_FULL_SCREEN) {
316- mCamera.moveYTo(0);
317- mCamera.moveZTo(0);
318- }
319- }
320-
321- protected void enableLocationFiltering(String label) {
322- if (mLocationFilter == false) {
323- mLocationFilter = true;
324- sHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
325- public void run() {
326- if (sHud.getAlpha() == 1.0f) {
327- if (mState == STATE_FULL_SCREEN) {
328- mInputProcessor.clearSelection();
329- setState(STATE_GRID_VIEW);
330- } else {
331- disableLocationFiltering();
332- }
333- } else {
334- sHud.setAlpha(1.0f);
335- }
336- }
337- });
338- }
339- }
340-
341- protected void disableLocationFiltering() {
342- if (mLocationFilter) {
343- mLocationFilter = false;
344- mMediaFeed.removeFilter();
345- sHud.getPathBar().popLabel();
346- }
347- }
348-
349- boolean goBack() {
350- if (mFeedAboutToChange) {
351- return false;
352- }
353- int state = mState;
354- if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
355- if (mLocationFilter) {
356- disableLocationFiltering();
357- setState(STATE_TIMELINE);
358- return true;
359- }
360- }
361- switch (state) {
362- case STATE_GRID_VIEW:
363- setState(STATE_MEDIA_SETS);
364- break;
365- case STATE_TIMELINE:
366- setState(STATE_GRID_VIEW);
367- break;
368- case STATE_FULL_SCREEN:
369- setState(STATE_GRID_VIEW);
370- mInputProcessor.clearSelection();
371- break;
372- default:
373- return false;
374- }
375- return true;
376- }
377-
378- public void endSlideshow() {
379- mSlideshowMode = false;
380- if (mWakeLock != null) {
381- if (mWakeLock.isHeld()) {
382- mWakeLock.release();
383- }
384- mWakeLock = null;
385- }
386- sHud.setAlpha(1.0f);
387- }
388-
389- @Override
390- public void onSensorChanged(RenderView view, SensorEvent event) {
391- mInputProcessor.onSensorChanged(view, event, mState);
392- }
393-
394- public DataSource getDataSource() {
395- if (mMediaFeed != null)
396- return mMediaFeed.getDataSource();
397- return null;
398- }
399-
400- public void setDataSource(DataSource dataSource) {
401- MediaFeed feed = mMediaFeed;
402- if (feed != null) {
403- feed.shutdown();
404- sDisplayList.clear();
405- mBackground.clear();
406- }
407- mMediaFeed = new MediaFeed(mContext, dataSource, this);
408- mMediaFeed.start();
409- }
410-
411- public IndexRange getVisibleRange() {
412- return sVisibleRange;
413- }
414-
415- public IndexRange getBufferedVisibleRange() {
416- return sBufferedVisibleRange;
417- }
418-
419- public IndexRange getCompleteRange() {
420- return sCompleteRange;
421- }
422-
423- private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
424- int retVal = Shared.INVALID;
425- int firstSlotIndex = 0;
426- int lastSlotIndex = 0;
427- IndexRange rangeToUse = sVisibleRange;
428- synchronized (rangeToUse) {
429- firstSlotIndex = rangeToUse.begin;
430- lastSlotIndex = rangeToUse.end;
431- }
432- Pool<Vector3f> pool = sTempVec;
433- float itemWidthBy2 = itemWidth * 0.5f;
434- float itemHeightBy2 = itemHeight * 0.5f;
435- Vector3f position = pool.create();
436- Vector3f deltaAnchorPosition = pool.create();
437- try {
438- deltaAnchorPosition.set(sDeltaAnchorPosition);
439- for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
440- GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
441- if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
442- position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
443- retVal = i;
444- break;
445- }
446- }
447- } finally {
448- pool.delete(deltaAnchorPosition);
449- pool.delete(position);
450- }
451- return retVal;
452- }
453-
454- void centerCameraForSlot(int slotIndex, float baseConvergence) {
455- float imageTheta = 0.0f;
456- DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
457- if (displayItem != null) {
458- imageTheta = displayItem.getImageTheta();
459- }
460- mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, sDeltaAnchorPositionUncommited,
461- mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
462- }
463-
464- boolean constrainCameraForSlot(int slotIndex) {
465- return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, sDeltaAnchorPosition, mCurrentFocusItemWidth,
466- mCurrentFocusItemHeight);
467- }
468-
469- // Called on render thread before rendering.
470- @Override
471- public boolean update(RenderView view, float timeElapsed) {
472- if (mFeedAboutToChange == false) {
473- mTimeElapsedSinceTransition += timeElapsed;
474- mTimeElapsedSinceGridViewReady += timeElapsed;
475- if (mTimeElapsedSinceGridViewReady >= 1.0f) {
476- mTimeElapsedSinceGridViewReady = 1.0f;
477- }
478- mTimeElapsedSinceStackViewReady += timeElapsed;
479- if (mTimeElapsedSinceStackViewReady >= 1.0f) {
480- mTimeElapsedSinceStackViewReady = 1.0f;
481- }
482- } else {
483- mTimeElapsedSinceTransition = 0;
484- }
485- if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
486- HudLayer hud = getHud();
487- hud.getPathBar().setHidden(true);
488- hud.getMenuBar().setHidden(true);
489- if (hud.getMode() != HudLayer.MODE_NORMAL)
490- hud.setMode(HudLayer.MODE_NORMAL);
491- }
492- if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
493- sHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
494- } else {
495- sHud.getPathBar().setAnimatedIcons(null);
496- }
497-
498- // In that case, we need to commit the respective Display Items when the
499- // feed was updated.
500- GridCamera camera = mCamera;
501- camera.update(timeElapsed);
502- DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
503- if (anchorDisplayItem != null && !sHud.getTimeBar().isDragged()) {
504- sHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
505- }
506- sDisplayList.update(timeElapsed);
507- mInputProcessor.update(timeElapsed);
508- mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
509- if (mState == STATE_FULL_SCREEN) {
510- sHud.autoHide(true);
511- } else {
512- sHud.autoHide(false);
513- sHud.setAlpha(1.0f);
514- }
515- GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
516- int numFullScreenQuads = fullscreenQuads.length;
517- for (int i = 0; i < numFullScreenQuads; ++i) {
518- fullscreenQuads[i].update(timeElapsed);
519- }
520- if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
521- mTimeElapsedSinceView += timeElapsed;
522- if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
523- // time to go to the next slide
524- mTimeElapsedSinceView = 0.0f;
525- changeFocusToNextSlot(0.5f);
526- mCamera.commitMoveInX();
527- mCamera.commitMoveInY();
528- }
529- }
530- if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
531- mCamera.moveYTo(-0.1f);
532- mCamera.commitMoveInY();
533- }
534- boolean dirty = mDrawManager.update(timeElapsed);
535- dirty |= mSlideshowMode;
536- dirty |= mFramesDirty > 0;
537- ++mFrameCount;
538- if (mFramesDirty > 0) {
539- --mFramesDirty;
540- }
541- try {
542- if (mMediaFeed != null && (mMediaFeed.getWaitingForMediaScanner())) {
543- // We limit the drawing of the frame so that the MediaScanner thread can do its work
544- Thread.sleep(200);
545- }
546- } catch (InterruptedException e) {
547-
548- }
549- if (sDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
550- || (mTimeElapsedSinceTransition > 0.0f && mTimeElapsedSinceTransition < 1.0f) || mSelectedAlpha != mTargetAlpha
551- // || (mAnimatedFov != mTargetFov)
552- || dirty)
553- return true;
554- else
555- return false;
556- }
557-
558- private void computeVisibleRange() {
559- if (mPerformingLayoutChange)
560- return;
561- if (sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited) == false) {
562- sDeltaAnchorPosition.set(sDeltaAnchorPositionUncommited);
563- }
564- mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, sDeltaAnchorPosition, sVisibleRange,
565- sBufferedVisibleRange, sCompleteRange, mState);
566- }
567-
568- private void computeVisibleItems() {
569- if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
570- return;
571- }
572- computeVisibleRange();
573- int deltaBegin = sBufferedVisibleRange.begin - sPreviousDataRange.begin;
574- int deltaEnd = sBufferedVisibleRange.end - sPreviousDataRange.end;
575- if (deltaBegin != 0 || deltaEnd != 0) {
576- // The delta has changed, we have to compute the display items again.
577- // We find the intersection range, these slots have not changed at all.
578- int firstVisibleSlotIndex = sBufferedVisibleRange.begin;
579- int lastVisibleSlotIndex = sBufferedVisibleRange.end;
580- sPreviousDataRange.begin = firstVisibleSlotIndex;
581- sPreviousDataRange.end = lastVisibleSlotIndex;
582-
583- Pool<Vector3f> pool = sTempVec;
584- Vector3f position = pool.create();
585- Vector3f deltaAnchorPosition = pool.create();
586- try {
587- MediaFeed feed = mMediaFeed;
588- DisplayList displayList = sDisplayList;
589- DisplayItem[] displayItems = sDisplayItems;
590- DisplaySlot[] displaySlots = sDisplaySlots;
591- int numDisplayItems = displayItems.length;
592- int numDisplaySlots = displaySlots.length;
593- ArrayList<MediaItem> visibleItems = sVisibleItems;
594- deltaAnchorPosition.set(sDeltaAnchorPosition);
595- LayoutInterface layout = mLayoutInterface;
596- GridCamera camera = mCamera;
597- for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
598- GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
599- MediaSet set = feed.getSetForSlot(i);
600- int indexIntoSlots = i - firstVisibleSlotIndex;
601-
602- if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
603- ArrayList<MediaItem> items = set.getItems();
604- displaySlots[indexIntoSlots].setMediaSet(set);
605- ArrayList<MediaItem> bestItems = sTempList;
606- if (mTimeElapsedSinceTransition < 1.0f) {
607- // We always show the same top thumbnails for a stack of albums
608- if (mState == STATE_MEDIA_SETS)
609- ArrayUtils.computeSortedIntersection(items, visibleItems, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
610- else
611- ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
612- }
613-
614- int numItemsInSet = set.getNumItems();
615- int numBestItems = bestItems.size();
616- int originallyFoundItems = numBestItems;
617- if (numBestItems < MAX_ITEMS_PER_SLOT) {
618- int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
619- for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
620- MediaItem item = items.get(currItemPos);
621- if (mTimeElapsedSinceTransition >= 1.0f || !bestItems.contains(item)) {
622- bestItems.add(item);
623- if (--itemsRemaining == 0) {
624- break;
625- }
626- }
627- }
628- }
629- numBestItems = bestItems.size();
630- int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
631- for (int j = 0; j < numBestItems; ++j) {
632- if (baseIndex + j >= numDisplayItems) {
633- break;
634- }
635- if (j >= numItemsInSet) {
636- displayItems[baseIndex + j] = null;
637- } else {
638- MediaItem item = bestItems.get(j);
639- if (item != null) {
640- DisplayItem displayItem = displayList.get(item);
641- if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
642- || (mState == STATE_GRID_VIEW && (mTimeElapsedSinceTransition > 1.0f || j >= originallyFoundItems))) {
643- displayItem.set(position, j, false);
644- displayItem.commit();
645- } else {
646- displayList.setPositionAndStackIndex(displayItem, position, j, true);
647- }
648- displayItems[baseIndex + j] = displayItem;
649- }
650- }
651- }
652- for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
653- displayItems[baseIndex + j] = null;
654- }
655- bestItems.clear();
656- }
657- }
658- if (mFeedChanged) {
659- mFeedChanged = false;
660- if (mInputProcessor != null && mState == STATE_FULL_SCREEN) {
661- int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
662- if (currentSelectedSlot > sCompleteRange.end)
663- currentSelectedSlot = sCompleteRange.end;
664- mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
665- }
666- if (mState == STATE_GRID_VIEW) {
667- MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
668- if (expandedSet != null) {
669- if (!sHud.getPathBar().getCurrentLabel().equals(expandedSet.mNoCountTitleString)) {
670- sHud.getPathBar().changeLabel(expandedSet.mNoCountTitleString);
671- }
672- }
673- }
674- if (mRequestFocusContentUri != null) {
675- // We have to find the item that has this contentUri
676- if (mState == STATE_FULL_SCREEN) {
677- int numSlots = sCompleteRange.end;
678- for (int i = 0; i < numSlots; ++i) {
679- MediaSet set = feed.getSetForSlot(i);
680- ArrayList<MediaItem> items = set.getItems();
681- int numItems = items.size();
682- for (int j = 0; j < numItems; ++j) {
683- if (items.get(j).mContentUri.equals(mRequestFocusContentUri)) {
684- mInputProcessor.setCurrentSelectedSlot(i);
685- break;
686- }
687- }
688- }
689- }
690- mRequestFocusContentUri = null;
691- }
692- }
693- } finally {
694- pool.delete(position);
695- pool.delete(deltaAnchorPosition);
696- }
697- // We keep upto 400 thumbnails in memory.
698- int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
699- int startMemoryRange = (sBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
700- if (mStartMemoryRange != startMemoryRange) {
701- mStartMemoryRange = startMemoryRange;
702- clearUnusedThumbnails();
703- }
704- }
705- }
706-
707- @Override
708- public void handleLowMemory() {
709- clearUnusedThumbnails();
710- GridDrawables.sStringTextureTable.clear();
711- mBackground.clearCache();
712- }
713-
714- // This method can be potentially expensive
715- public void clearUnusedThumbnails() {
716- sDisplayList.clearExcept(sDisplayItems);
717- }
718-
719- @Override
720- public void onSurfaceCreated(RenderView view, GL11 gl) {
721- sDisplayList.clear();
722- sHud.clear();
723- sHud.reset();
724- GridDrawables.sStringTextureTable.clear();
725- mDrawables.onSurfaceCreated(view, gl);
726- mBackground.clear();
727- }
728-
729- @Override
730- public void onSurfaceChanged(RenderView view, int width, int height) {
731- mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
732- view.setFov(mCamera.mFov);
733- setState(mState);
734- }
735-
736- // Renders the node in a given pass.
737- public void renderOpaque(RenderView view, GL11 gl) {
738- GridCamera camera = mCamera;
739- int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
740- computeVisibleItems();
741-
742- gl.glMatrixMode(GL11.GL_MODELVIEW);
743- gl.glLoadIdentity();
744- GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
745- camera.mUpX, camera.mUpY, camera.mUpZ);
746- view.setAlpha(1.0f);
747- if (mSelectedAlpha != 1.0f) {
748- gl.glEnable(GL11.GL_BLEND);
749- gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
750- view.setAlpha(mSelectedAlpha);
751- }
752- if (selectedSlotIndex != Shared.INVALID) {
753- mTargetAlpha = 0.0f;
754- } else {
755- mTargetAlpha = 1.0f;
756- }
757- mDrawManager.prepareDraw(sBufferedVisibleRange, sVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
758- mInputProcessor.isFocusItemPressed());
759- if (mSelectedAlpha != 0.0f) {
760- mDrawManager.drawThumbnails(view, gl, mState);
761- }
762- if (mSelectedAlpha != 1.0f) {
763- gl.glDisable(GL11.GL_BLEND);
764- }
765- // We draw the selected slotIndex.
766- if (selectedSlotIndex != Shared.INVALID) {
767- mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
768- mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
769- mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
770- }
771- view.setAlpha(mSelectedAlpha);
772- }
773-
774- public void renderBlended(RenderView view, GL11 gl) {
775- // We draw the placeholder for all visible slots.
776- if (sHud != null && mDrawManager != null) {
777- mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, sHud.getMode(), mTimeElapsedSinceStackViewReady,
778- mTimeElapsedSinceGridViewReady, sBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
779- || mMediaFeed.isLoading());
780- }
781- }
782-
783- public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
784- if (mPerformingLayoutChange || !sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited)) {
785- return;
786- }
787-
788- mTimeElapsedSinceTransition = 0.0f;
789- mPerformingLayoutChange = true;
790- LayoutInterface layout = mLayoutInterface;
791- if (oldLayout == null) {
792- oldLayout = sfullScreenLayoutInterface;
793- }
794- GridCamera camera = mCamera;
795- if (currentAnchorSlotIndex == Shared.INVALID) {
796- currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
797- if (mCurrentExpandedSlot != Shared.INVALID) {
798- currentAnchorSlotIndex = mCurrentExpandedSlot;
799- }
800- int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
801- if (selectedSlotIndex != Shared.INVALID) {
802- currentAnchorSlotIndex = selectedSlotIndex;
803- }
804- }
805- if (newAnchorSlotIndex == Shared.INVALID) {
806- newAnchorSlotIndex = currentAnchorSlotIndex;
807- }
808- int itemHeight = camera.mItemHeight;
809- int itemWidth = camera.mItemWidth;
810- Pool<Vector3f> pool = sTempVec;
811- Vector3f deltaAnchorPosition = pool.create();
812- Vector3f currentSlotPosition = pool.create();
813- try {
814- deltaAnchorPosition.set(0, 0, 0);
815- if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
816- layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
817- oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
818- currentSlotPosition.subtract(sDeltaAnchorPosition);
819- deltaAnchorPosition.subtract(currentSlotPosition);
820- deltaAnchorPosition.y = 0;
821- deltaAnchorPosition.z = 0;
822- }
823- sDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
824- } finally {
825- pool.delete(deltaAnchorPosition);
826- pool.delete(currentSlotPosition);
827- }
828- centerCameraForSlot(newAnchorSlotIndex, 1.0f);
829- mCurrentExpandedSlot = Shared.INVALID;
830-
831- // Force recompute of visible items and their positions.
832- ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
833- ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
834- ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
835- forceRecomputeVisibleRange();
836- mPerformingLayoutChange = false;
837- }
838-
839- private void forceRecomputeVisibleRange() {
840- sPreviousDataRange.begin = Shared.INVALID;
841- sPreviousDataRange.end = Shared.INVALID;
842- if (mView != null) {
843- mView.requestRender();
844- }
845- }
846-
847- // called on background thread
848- public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
849- if (!needsLayout) {
850- mFeedChanged = true;
851- forceRecomputeVisibleRange();
852- if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
853- sHud.setFeed(feed, mState, needsLayout);
854- return;
855- }
856-
857- while (mPerformingLayoutChange == true) {
858- Thread.yield();
859- }
860- if (mState == STATE_GRID_VIEW) {
861- if (sHud != null) {
862- MediaSet set = feed.getCurrentSet();
863- if (set != null && !mLocationFilter)
864- sHud.getPathBar().changeLabel(set.mNoCountTitleString);
865- }
866- }
867- DisplayItem[] displayItems = sDisplayItems;
868- int firstBufferedVisibleSlotIndex = sBufferedVisibleRange.begin;
869- int lastBufferedVisibleSlotIndex = sBufferedVisibleRange.end;
870- int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
871- if (mCurrentExpandedSlot != Shared.INVALID) {
872- currentlyVisibleSlotIndex = mCurrentExpandedSlot;
873- }
874- MediaItem anchorItem = null;
875- ArrayList<MediaItem> visibleItems = sVisibleItems;
876- visibleItems.clear();
877- visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
878- if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
879- && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
880- int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
881- for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
882- DisplayItem displayItem = displayItems[baseIndex + i];
883- if (displayItem != null) {
884- if (anchorItem == null) {
885- anchorItem = displayItem.mItemRef;
886- }
887- visibleItems.add(displayItem.mItemRef);
888- }
889- }
890- }
891- // We want to add items from the middle.
892- int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
893- int midPoint = (lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex) / 2;
894- int length = displayItems.length;
895- for (int i = 0; i < numItems; ++i) {
896- int index = midPoint + Shared.midPointIterator(i);
897- int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
898- if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
899- for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
900- DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
901- if (displayItem != null) {
902- MediaItem item = displayItem.mItemRef;
903- if (item != anchorItem) {
904- visibleItems.add(item);
905- }
906- }
907- }
908- }
909- }
910- int newSlotIndex = Shared.INVALID;
911- if (anchorItem != null) {
912- // We try to find the anchor item in the new feed.
913- int numSlots = feed.getNumSlots();
914- for (int i = 0; i < numSlots; ++i) {
915- MediaSet set = feed.getSetForSlot(i);
916- if (set != null && set.containsItem(anchorItem)) {
917- newSlotIndex = i;
918- break;
919- }
920- }
921- }
922-
923- if (anchorItem != null && newSlotIndex == Shared.INVALID) {
924- int numSlots = feed.getNumSlots();
925- MediaSet parentSet = anchorItem.mParentMediaSet;
926- for (int i = 0; i < numSlots; ++i) {
927- MediaSet set = feed.getSetForSlot(i);
928- if (set != null && set.mId == parentSet.mId) {
929- newSlotIndex = i;
930- break;
931- }
932- }
933- }
934-
935- // We must create a new display store now since the data has changed.
936- if (newSlotIndex != Shared.INVALID) {
937- if (mState == STATE_MEDIA_SETS) {
938- sDisplayList.clearExcept(displayItems);
939- }
940- onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
941- } else {
942- forceRecomputeVisibleRange();
943- }
944- mCurrentExpandedSlot = Shared.INVALID;
945- mFeedAboutToChange = false;
946- mFeedChanged = true;
947- if (feed != null) {
948- if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
949- sHud.setFeed(feed, mState, needsLayout);
950- }
951- if (mView != null) {
952- mView.requestRender();
953- }
954- }
955-
956- public DisplayItem getRepresentativeDisplayItem() {
957- int slotIndex = Shared.INVALID;
958- if (mInputProcessor != null) {
959- slotIndex = mInputProcessor.getCurrentFocusSlot();
960- }
961- if (slotIndex == Shared.INVALID) {
962- slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
963- }
964- int index = (slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
965- if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
966- return sDisplayItems[index];
967- } else {
968- return null;
969- }
970- }
971-
972- public DisplayItem getAnchorDisplayItem(int type) {
973- int slotIndex = getAnchorSlotIndex(type);
974- return sDisplayItems[(slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT];
975- }
976-
977- public float getScrollPosition() {
978- return (mCamera.mLookAtX * mCamera.mScale + sDeltaAnchorPosition.x); // in
979- // pixels
980- }
981-
982- public DisplayItem getDisplayItemForScrollPosition(float posX) {
983- Pool<Vector3f> pool = sTempVecAlt;
984- MediaFeed feed = mMediaFeed;
985- int itemWidth = mCamera.mItemWidth;
986- int itemHeight = mCamera.mItemHeight;
987- GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
988- float absolutePosX = posX;
989- int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
990- int right = feed == null ? 0 : (int) (feed.getNumSlots());
991- int retSlot = left;
992- Vector3f position = pool.create();
993- try {
994- for (int i = left; i < right; ++i) {
995- gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
996- retSlot = i;
997- if (position.x >= absolutePosX) {
998- break;
999- }
1000- }
1001- } finally {
1002- pool.delete(position);
1003- }
1004- if (mFeedAboutToChange) {
1005- return null;
1006- }
1007- right = feed == null ? 0 : feed.getNumSlots();
1008- if (right == 0) {
1009- return null;
1010- }
1011-
1012- if (retSlot >= right)
1013- retSlot = right - 1;
1014- MediaSet set = feed.getSetForSlot(retSlot);
1015- if (set != null) {
1016- ArrayList<MediaItem> items = set.getItems();
1017- if (items != null && set.getNumItems() > 0) {
1018- return (sDisplayList.get(items.get(0)));
1019- }
1020- }
1021- return null;
1022- }
1023-
1024- // Returns the top left-most item.
1025- public int getAnchorSlotIndex(int anchorType) {
1026- int retVal = 0;
1027- switch (anchorType) {
1028- case ANCHOR_LEFT:
1029- retVal = sVisibleRange.begin;
1030- break;
1031- case ANCHOR_RIGHT:
1032- retVal = sVisibleRange.end;
1033- break;
1034- case ANCHOR_CENTER:
1035- retVal = (sVisibleRange.begin + sVisibleRange.end) / 2;
1036- break;
1037- }
1038- return retVal;
1039- }
1040-
1041- DisplayItem getDisplayItemForSlotId(int slotId) {
1042- int index = slotId - sBufferedVisibleRange.begin;
1043- if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
1044- return sDisplayItems[index * MAX_ITEMS_PER_SLOT];
1045- }
1046- return null;
1047- }
1048-
1049- boolean changeFocusToNextSlot(float convergence) {
1050- int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
1051- boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
1052- if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
1053- endSlideshow();
1054- sHud.setAlpha(1.0f);
1055- }
1056- return retVal;
1057- }
1058-
1059- boolean changeFocusToSlot(int slotId, float convergence) {
1060- mZoomValue = 1.0f;
1061- int index = slotId - sBufferedVisibleRange.begin;
1062- if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
1063- DisplayItem displayItem = sDisplayItems[index * MAX_ITEMS_PER_SLOT];
1064- if (displayItem != null) {
1065- MediaItem item = displayItem.mItemRef;
1066- sHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
1067- if (slotId != Shared.INVALID && slotId <= sCompleteRange.end) {
1068- mInputProcessor.setCurrentFocusSlot(slotId);
1069- centerCameraForSlot(slotId, convergence);
1070- return true;
1071- } else {
1072- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
1073- return false;
1074- }
1075- }
1076- }
1077- return false;
1078- }
1079-
1080- boolean changeFocusToPreviousSlot(float convergence) {
1081- return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
1082- }
1083-
1084- public ArrayList<MediaBucket> getSelectedBuckets() {
1085- return sBucketList.get();
1086- }
1087-
1088- public void selectAll() {
1089- if (mState != STATE_FULL_SCREEN) {
1090- int numSlots = sCompleteRange.end + 1;
1091- for (int i = 0; i < numSlots; ++i) {
1092- addSlotToSelectedItems(i, false, false);
1093- }
1094- updateCountOfSelectedItems();
1095- } else {
1096- addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
1097- }
1098- }
1099-
1100- public void deselectOrCancelSelectMode() {
1101- if (sBucketList.size() == 0) {
1102- sHud.cancelSelection();
1103- } else {
1104- sBucketList.clear();
1105- updateCountOfSelectedItems();
1106- }
1107- }
1108-
1109- public void deselectAll() {
1110- sHud.cancelSelection();
1111- sBucketList.clear();
1112- updateCountOfSelectedItems();
1113- }
1114-
1115- public void deleteSelection() {
1116- // Delete the selection and exit selection mode.
1117- mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
1118- deselectAll();
1119-
1120- // If the current set is now empty, return to the parent set.
1121- if (sCompleteRange.isEmpty()) {
1122- goBack(); // TODO(venkat): This does not work most of the time, can you take a look?
1123- }
1124- }
1125-
1126- void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
1127- if (mFeedAboutToChange == false) {
1128- MediaFeed feed = mMediaFeed;
1129- sBucketList.add(slotId, feed, removeIfAlreadyAdded);
1130- if (updateCount) {
1131- updateCountOfSelectedItems();
1132- if (sBucketList.size() == 0)
1133- deselectAll();
1134- }
1135- }
1136- }
1137-
1138- private void updateCountOfSelectedItems() {
1139- sHud.updateNumItemsSelected(sBucketList.size());
1140- }
1141-
1142- public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
1143- return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * Gallery.PIXEL_DENSITY), mCamera.mItemHeight
1144- + (int) (100 * Gallery.PIXEL_DENSITY));
1145- }
1146-
1147- public int getSlotIndexForScreenPosition(int posX, int posY) {
1148- return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
1149- }
1150-
1151- private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
1152- Pool<Vector3f> pool = sTempVec;
1153- int retVal = 0;
1154- Vector3f worldPos = pool.create();
1155- try {
1156- GridCamera camera = mCamera;
1157- camera.convertToCameraSpace(posX, posY, 0, worldPos);
1158- // slots are expressed in pixels as well
1159- worldPos.x *= camera.mScale;
1160- worldPos.y *= camera.mScale;
1161- // we ignore z
1162- retVal = hitTest(worldPos, itemWidth, itemHeight);
1163- } finally {
1164- pool.delete(worldPos);
1165- }
1166- return retVal;
1167- }
1168-
1169- public boolean tapGesture(int slotIndex, boolean metadata) {
1170- MediaFeed feed = mMediaFeed;
1171- if (!feed.isClustered()) {
1172- // It is not clustering.
1173- if (!feed.hasExpandedMediaSet()) {
1174- if (feed.canExpandSet(slotIndex)) {
1175- mCurrentExpandedSlot = slotIndex;
1176- feed.expandMediaSet(slotIndex);
1177- setState(STATE_GRID_VIEW);
1178- }
1179- return false;
1180- } else {
1181- return true;
1182- }
1183- } else {
1184- // Select a cluster, and recompute a new cluster within this cluster.
1185- mCurrentExpandedSlot = slotIndex;
1186- goBack();
1187- if (metadata) {
1188- DisplaySlot slot = sDisplaySlots[slotIndex - sBufferedVisibleRange.begin];
1189- if (slot.hasValidLocation()) {
1190- MediaSet set = slot.getMediaSet();
1191- if (set.mReverseGeocodedLocation != null) {
1192- enableLocationFiltering(set.mReverseGeocodedLocation);
1193- }
1194- feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude, set.mMaxLonLongitude));
1195- }
1196- }
1197- return false;
1198- }
1199- }
1200-
1201- public void onTimeChanged(TimeBar timebar) {
1202- if (mFeedAboutToChange) {
1203- return;
1204- }
1205- // TODO lot of optimization possible here
1206- MediaItem item = timebar.getItem();
1207- MediaFeed feed = mMediaFeed;
1208- int numSlots = feed.getNumSlots();
1209- for (int i = 0; i < numSlots; ++i) {
1210- MediaSet set = feed.getSetForSlot(i);
1211- if (set == null) {
1212- return;
1213- }
1214- ArrayList<MediaItem> items = set.getItems();
1215- if (items == null || set.getNumItems() == 0) {
1216- return;
1217- }
1218- if (items.contains(item)) {
1219- centerCameraForSlot(i, 1.0f);
1220- break;
1221- }
1222- }
1223- }
1224-
1225- public void onFeedAboutToChange(MediaFeed feed) {
1226- mFeedAboutToChange = true;
1227- mTimeElapsedSinceTransition = 0;
1228- }
1229-
1230- public void startSlideshow() {
1231- endSlideshow();
1232- mSlideshowMode = true;
1233- mZoomValue = 1.0f;
1234- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1235- mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
1236- sHud.setAlpha(0);
1237- PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1238- mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
1239- mWakeLock.acquire();
1240- }
1241-
1242- public void enterSelectionMode() {
1243- mSlideshowMode = false;
1244- sHud.enterSelectionMode();
1245- int currentSlot = mInputProcessor.getCurrentSelectedSlot();
1246- if (currentSlot == Shared.INVALID) {
1247- currentSlot = mInputProcessor.getCurrentFocusSlot();
1248- }
1249- addSlotToSelectedItems(currentSlot, false, true);
1250- }
1251-
1252- private float getFillScreenZoomValue() {
1253- return GridCameraManager.getFillScreenZoomValue(mCamera, sTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
1254- }
1255-
1256- public void zoomInToSelectedItem() {
1257- mSlideshowMode = false;
1258- float potentialZoomValue = getFillScreenZoomValue();
1259- if (mZoomValue < potentialZoomValue) {
1260- mZoomValue = potentialZoomValue;
1261- } else {
1262- mZoomValue *= 3.0f;
1263- }
1264- if (mZoomValue > 6.0f) {
1265- mZoomValue = 6.0f;
1266- }
1267- sHud.setAlpha(1.0f);
1268- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1269- }
1270-
1271- public void zoomOutFromSelectedItem() {
1272- mSlideshowMode = false;
1273- if (mZoomValue == getFillScreenZoomValue()) {
1274- mZoomValue = 1.0f;
1275- } else {
1276- mZoomValue /= 3.0f;
1277- }
1278- if (mZoomValue < 1.0f) {
1279- mZoomValue = 1.0f;
1280- }
1281- sHud.setAlpha(1.0f);
1282- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1283- }
1284-
1285- public void rotateSelectedItems(float f) {
1286- MediaBucketList bucketList = sBucketList;
1287- ArrayList<MediaBucket> mediaBuckets = bucketList.get();
1288- DisplayList displayList = sDisplayList;
1289- int numBuckets = mediaBuckets.size();
1290- for (int i = 0; i < numBuckets; ++i) {
1291- MediaBucket bucket = mediaBuckets.get(i);
1292- ArrayList<MediaItem> mediaItems = bucket.mediaItems;
1293- if (mediaItems != null) {
1294- int numMediaItems = mediaItems.size();
1295- for (int j = 0; j < numMediaItems; ++j) {
1296- MediaItem item = mediaItems.get(j);
1297- DisplayItem displayItem = displayList.get(item);
1298- displayItem.rotateImageBy(f);
1299- displayList.addToAnimatables(displayItem);
1300- }
1301- }
1302- }
1303- if (mState == STATE_FULL_SCREEN) {
1304- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1305- }
1306- mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
1307- // we recreate these displayitems from the cache
1308- }
1309-
1310- public void cropSelectedItem() {
1311-
1312- }
1313-
1314- @Override
1315- public boolean onTouchEvent(MotionEvent event) {
1316- return mInputProcessor.onTouchEvent(event);
1317- }
1318-
1319- @Override
1320- public boolean onKeyDown(int keyCode, KeyEvent event) {
1321- if (mInputProcessor != null)
1322- return mInputProcessor.onKeyDown(keyCode, event, mState);
1323- return false;
1324- }
1325-
1326- public boolean inSlideShowMode() {
1327- return mSlideshowMode;
1328- }
1329-
1330- public boolean noDeleteMode() {
1331- return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
1332- }
1333-
1334- public float getZoomValue() {
1335- return mZoomValue;
1336- }
1337-
1338- public boolean feedAboutToChange() {
1339- return mFeedAboutToChange;
1340- }
1341-
1342- public boolean isInAlbumMode() {
1343- return mInAlbum;
1344- }
1345-
1346- public Vector3f getDeltaAnchorPosition() {
1347- return sDeltaAnchorPosition;
1348- }
1349-
1350- public int getExpandedSlot() {
1351- return mCurrentExpandedSlot;
1352- }
1353-
1354- public GridLayoutInterface getLayoutInterface() {
1355- return (GridLayoutInterface) mLayoutInterface;
1356- }
1357-
1358- public void setZoomValue(float f) {
1359- mZoomValue = f;
1360- centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1361- }
1362-
1363- public void setPickIntent(boolean b) {
1364- mPickIntent = b;
1365- sHud.getPathBar().popLabel();
1366- sHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
1367- new Runnable() {
1368- public void run() {
1369- if (sHud.getAlpha() == 1.0f) {
1370- if (!mFeedAboutToChange) {
1371- setState(STATE_MEDIA_SETS);
1372- }
1373- } else {
1374- sHud.setAlpha(1.0f);
1375- }
1376- }
1377- });
1378- }
1379-
1380- public boolean getPickIntent() {
1381- return mPickIntent;
1382- }
1383-
1384- public void setViewIntent(boolean b, final String setName) {
1385- mViewIntent = b;
1386- if (b) {
1387- mMediaFeed.expandMediaSet(0);
1388- setState(STATE_GRID_VIEW);
1389- // We need to make sure we haven't pushed the same label twice
1390- if (sHud.getPathBar().getNumLevels() == 1) {
1391- sHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
1392- public void run() {
1393- if (mFeedAboutToChange) {
1394- return;
1395- }
1396- if (sHud.getAlpha() == 1.0f) {
1397- disableLocationFiltering();
1398- if (mInputProcessor != null)
1399- mInputProcessor.clearSelection();
1400- setState(STATE_GRID_VIEW);
1401- } else {
1402- sHud.setAlpha(1.0f);
1403- }
1404- }
1405- });
1406- }
1407- }
1408- }
1409-
1410- public boolean getViewIntent() {
1411- return mViewIntent;
1412- }
1413-
1414- public void setSingleImage(boolean noDeleteMode) {
1415- mNoDeleteMode = noDeleteMode;
1416- mInputProcessor.setCurrentSelectedSlot(0);
1417- }
1418-
1419- public MediaFeed getFeed() {
1420- return mMediaFeed;
1421- }
1422-
1423- public void markDirty(int numFrames) {
1424- mFramesDirty = numFrames;
1425- }
1426-
1427- public void focusItem(String contentUri) {
1428- mRequestFocusContentUri = contentUri;
1429- mMediaFeed.updateListener(false);
1430- }
16+ public static final int STATE_MEDIA_SETS = 0;
17+ public static final int STATE_GRID_VIEW = 1;
18+ public static final int STATE_FULL_SCREEN = 2;
19+ public static final int STATE_TIMELINE = 3;
20+
21+ public static final int ANCHOR_LEFT = 0;
22+ public static final int ANCHOR_RIGHT = 1;
23+ public static final int ANCHOR_CENTER = 2;
24+
25+ public static final int MAX_ITEMS_PER_SLOT = 12;
26+ public static final int MAX_DISPLAYED_ITEMS_PER_SLOT = 4;
27+ public static final int MAX_DISPLAY_SLOTS = 96;
28+ public static final int MAX_ITEMS_DRAWABLE = MAX_ITEMS_PER_SLOT * MAX_DISPLAY_SLOTS;
29+
30+ private static final float SLIDESHOW_TRANSITION_TIME = 3.5f;
31+
32+ private static HudLayer sHud;
33+ private int mState;
34+ private static final IndexRange sBufferedVisibleRange = new IndexRange();
35+ private static final IndexRange sVisibleRange = new IndexRange();
36+ private static final IndexRange sPreviousDataRange = new IndexRange();
37+ private static final IndexRange sCompleteRange = new IndexRange();
38+
39+ private static final Pool<Vector3f> sTempVec;
40+ private static final Pool<Vector3f> sTempVecAlt;
41+ static {
42+ Vector3f[] vectorPool = new Vector3f[128];
43+ int length = vectorPool.length;
44+ for (int i = 0; i < length; ++i) {
45+ vectorPool[i] = new Vector3f();
46+ }
47+ Vector3f[] vectorPoolRenderThread = new Vector3f[128];
48+ length = vectorPoolRenderThread.length;
49+ for (int i = 0; i < length; ++i) {
50+ vectorPoolRenderThread[i] = new Vector3f();
51+ }
52+ sTempVec = new Pool<Vector3f>(vectorPool);
53+ sTempVecAlt = new Pool<Vector3f>(vectorPoolRenderThread);
54+ }
55+
56+ private static final ArrayList<MediaItem> sTempList = new ArrayList<MediaItem>();
57+ private static final MediaItem[] sTempHash = new MediaItem[64];
58+
59+ private static final Vector3f sDeltaAnchorPositionUncommited = new Vector3f();
60+ private static Vector3f sDeltaAnchorPosition = new Vector3f();
61+
62+ // The display primitives.
63+ private GridDrawables mDrawables;
64+ private float mSelectedAlpha = 0.0f;
65+ private float mTargetAlpha = 0.0f;
66+
67+ private GridCamera mCamera;
68+ private GridCameraManager mCameraManager;
69+ private GridDrawManager mDrawManager;
70+ private GridInputProcessor mInputProcessor;
71+
72+ private boolean mFeedAboutToChange;
73+ private boolean mPerformingLayoutChange;
74+ private boolean mFeedChanged;
75+
76+ private final LayoutInterface mLayoutInterface;
77+ private static final LayoutInterface sfullScreenLayoutInterface = new GridLayoutInterface(1);
78+
79+ private MediaFeed mMediaFeed;
80+ private boolean mInAlbum = false;
81+ private int mCurrentExpandedSlot;
82+
83+ private static final DisplayList sDisplayList = new DisplayList();
84+ private static final DisplayItem[] sDisplayItems = new DisplayItem[MAX_ITEMS_DRAWABLE];
85+ private static final DisplaySlot[] sDisplaySlots = new DisplaySlot[MAX_DISPLAY_SLOTS];
86+ private static ArrayList<MediaItem> sVisibleItems;
87+
88+ private float mTimeElapsedSinceTransition;
89+ private BackgroundLayer mBackground;
90+ private boolean mLocationFilter;
91+ private float mZoomValue = 1.0f;
92+ private float mCurrentFocusItemWidth = 1.0f;
93+ private float mCurrentFocusItemHeight = 1.0f;
94+ private float mTimeElapsedSinceGridViewReady = 0.0f;
95+
96+ private boolean mSlideshowMode;
97+ private boolean mNoDeleteMode = false;
98+ private float mTimeElapsedSinceView;
99+ private static final MediaBucketList sBucketList = new MediaBucketList();
100+ private float mTimeElapsedSinceStackViewReady;
101+
102+ private Context mContext;
103+ private RenderView mView;
104+ private boolean mPickIntent;
105+ private boolean mViewIntent;
106+ private WakeLock mWakeLock;
107+ private int mStartMemoryRange;
108+ private int mFramesDirty;
109+ private String mRequestFocusContentUri;
110+ private int mFrameCount;
111+ private boolean mNeedsInit;
112+
113+ public GridLayer(Context context, int itemWidth, int itemHeight, LayoutInterface layoutInterface, RenderView view) {
114+ mBackground = new BackgroundLayer(this);
115+ mContext = context;
116+ mView = view;
117+ mNeedsInit = true;
118+
119+ DisplaySlot[] displaySlots = sDisplaySlots;
120+ for (int i = 0; i < MAX_DISPLAY_SLOTS; ++i) {
121+ DisplaySlot slot = new DisplaySlot();
122+ displaySlots[i] = slot;
123+ }
124+ mLayoutInterface = layoutInterface;
125+ mCamera = new GridCamera(0, 0, itemWidth, itemHeight);
126+ mDrawables = new GridDrawables(itemWidth, itemHeight);
127+ sBufferedVisibleRange.set(Shared.INVALID, Shared.INVALID);
128+ sVisibleRange.set(Shared.INVALID, Shared.INVALID);
129+ sCompleteRange.set(Shared.INVALID, Shared.INVALID);
130+ sPreviousDataRange.set(Shared.INVALID, Shared.INVALID);
131+ sDeltaAnchorPosition.set(0, 0, 0);
132+ sDeltaAnchorPositionUncommited.set(0, 0, 0);
133+ sBucketList.clear();
134+
135+ sVisibleItems = new ArrayList<MediaItem>();
136+ if (sHud == null) {
137+ sHud = new HudLayer(context);
138+ }
139+ sHud.setContext(context);
140+ sHud.setGridLayer(this);
141+ sHud.getPathBar().clear();
142+ sHud.setGridLayer(this);
143+ sHud.getTimeBar().setListener(this);
144+ sHud.getPathBar().pushLabel(R.drawable.icon_home_small, context.getResources().getString(R.string.app_name),
145+ new Runnable() {
146+ public void run() {
147+ if (sHud.getAlpha() == 1.0f) {
148+ if (!mFeedAboutToChange) {
149+ setState(STATE_MEDIA_SETS);
150+ }
151+ } else {
152+ sHud.setAlpha(1.0f);
153+ }
154+ }
155+ });
156+ mCameraManager = new GridCameraManager(mCamera);
157+ mDrawManager = new GridDrawManager(context, mCamera, mDrawables, sDisplayList, sDisplayItems, sDisplaySlots);
158+ mInputProcessor = new GridInputProcessor(context, mCamera, this, mView, sTempVec, sDisplayItems);
159+ }
160+
161+ public HudLayer getHud() {
162+ return sHud;
163+ }
164+
165+ public void shutdown() {
166+ if (mMediaFeed != null) {
167+ mMediaFeed.shutdown();
168+ }
169+ mContext = null;
170+ mBackground = null;
171+ sBucketList.clear();
172+ mView = null;
173+ }
174+
175+ public void stop() {
176+ endSlideshow();
177+ mBackground.clear();
178+ handleLowMemory();
179+ }
180+
181+ @Override
182+ public void generate(RenderView view, RenderView.Lists lists) {
183+ lists.updateList.add(this);
184+ lists.opaqueList.add(this);
185+ mBackground.generate(view, lists);
186+ lists.blendedList.add(this);
187+ lists.hitTestList.add(this);
188+ sHud.generate(view, lists);
189+ }
190+
191+ @Override
192+ protected void onSizeChanged() {
193+ sHud.setSize(mWidth, mHeight);
194+ sHud.setAlpha(1.0f);
195+ mBackground.setSize(mWidth, mHeight);
196+ mTimeElapsedSinceTransition = 0.0f;
197+ if (mView != null) {
198+ mView.requestRender();
199+ }
200+ }
201+
202+ public int getState() {
203+ return mState;
204+ }
205+
206+ public void setState(int state) {
207+ boolean feedUnchanged = false;
208+ if (mState == state) {
209+ feedUnchanged = true;
210+ }
211+ GridLayoutInterface layoutInterface = (GridLayoutInterface) mLayoutInterface;
212+ GridLayoutInterface oldLayout = (GridLayoutInterface) sfullScreenLayoutInterface;
213+ oldLayout.mNumRows = layoutInterface.mNumRows;
214+ oldLayout.mSpacingX = layoutInterface.mSpacingX;
215+ oldLayout.mSpacingY = layoutInterface.mSpacingY;
216+ GridCamera camera = mCamera;
217+ int numMaxRows = (camera.mHeight >= camera.mWidth) ? 4 : 3;
218+ MediaFeed feed = mMediaFeed;
219+ boolean performLayout = true;
220+ mZoomValue = 1.0f;
221+ float yStretch = camera.mDefaultAspectRatio / camera.mAspectRatio;
222+ if (yStretch < 1.0f) {
223+ yStretch = 1.0f;
224+ }
225+ switch (state) {
226+ case STATE_GRID_VIEW:
227+ mTimeElapsedSinceGridViewReady = 0.0f;
228+ if (feed != null && feedUnchanged == false) {
229+ boolean updatedData = feed.restorePreviousClusteringState();
230+ if (updatedData) {
231+ performLayout = false;
232+ }
233+ }
234+ layoutInterface.mNumRows = numMaxRows;
235+ layoutInterface.mSpacingX = (int) (10 * Gallery.PIXEL_DENSITY);
236+ layoutInterface.mSpacingY = (int) (10 * Gallery.PIXEL_DENSITY);
237+ if (mState == STATE_MEDIA_SETS) {
238+ // Entering album.
239+ mInAlbum = true;
240+ MediaSet set = feed.getCurrentSet();
241+ int icon = mDrawables.getIconForSet(set, true);
242+ if (set != null) {
243+ sHud.getPathBar().pushLabel(icon, set.mNoCountTitleString, new Runnable() {
244+ public void run() {
245+ if (mFeedAboutToChange) {
246+ return;
247+ }
248+ if (sHud.getAlpha() == 1.0f) {
249+ disableLocationFiltering();
250+ mInputProcessor.clearSelection();
251+ setState(STATE_GRID_VIEW);
252+ } else {
253+ sHud.setAlpha(1.0f);
254+ }
255+ }
256+ });
257+ }
258+ }
259+ if (mState == STATE_FULL_SCREEN) {
260+ sHud.getPathBar().popLabel();
261+ }
262+ break;
263+ case STATE_TIMELINE:
264+ mTimeElapsedSinceStackViewReady = 0.0f;
265+ if (feed != null && feedUnchanged == false) {
266+ feed.performClustering();
267+ performLayout = false;
268+ }
269+ disableLocationFiltering();
270+ layoutInterface.mNumRows = numMaxRows - 1;
271+ layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
272+ layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
273+ break;
274+ case STATE_FULL_SCREEN:
275+ layoutInterface.mNumRows = 1;
276+ layoutInterface.mSpacingX = (int) (40 * Gallery.PIXEL_DENSITY);
277+ layoutInterface.mSpacingY = (int) (40 * Gallery.PIXEL_DENSITY);
278+ if (mState != STATE_FULL_SCREEN) {
279+ sHud.getPathBar().pushLabel(R.drawable.ic_fs_details, "", new Runnable() {
280+ public void run() {
281+ if (sHud.getAlpha() == 1.0f) {
282+ sHud.swapFullscreenLabel();
283+ }
284+ sHud.setAlpha(1.0f);
285+ }
286+ });
287+ }
288+ break;
289+ case STATE_MEDIA_SETS:
290+ mTimeElapsedSinceStackViewReady = 0.0f;
291+ if (feed != null && feedUnchanged == false) {
292+ feed.restorePreviousClusteringState();
293+ feed.expandMediaSet(Shared.INVALID);
294+ performLayout = false;
295+ }
296+ disableLocationFiltering();
297+ mInputProcessor.clearSelection();
298+ layoutInterface.mNumRows = numMaxRows - 1;
299+ layoutInterface.mSpacingX = (int) (100 * Gallery.PIXEL_DENSITY);
300+ layoutInterface.mSpacingY = (int) (70 * Gallery.PIXEL_DENSITY * yStretch);
301+ if (mInAlbum) {
302+ if (mState == STATE_FULL_SCREEN) {
303+ sHud.getPathBar().popLabel();
304+ }
305+ sHud.getPathBar().popLabel();
306+ mInAlbum = false;
307+ }
308+ break;
309+ }
310+ mState = state;
311+ sHud.onGridStateChanged();
312+ if (performLayout && mFeedAboutToChange == false) {
313+ onLayout(Shared.INVALID, Shared.INVALID, oldLayout);
314+ }
315+ if (state != STATE_FULL_SCREEN) {
316+ mCamera.moveYTo(0);
317+ mCamera.moveZTo(0);
318+ }
319+ }
320+
321+ protected void enableLocationFiltering(String label) {
322+ if (mLocationFilter == false) {
323+ mLocationFilter = true;
324+ sHud.getPathBar().pushLabel(R.drawable.icon_location_small, label, new Runnable() {
325+ public void run() {
326+ if (sHud.getAlpha() == 1.0f) {
327+ if (mState == STATE_FULL_SCREEN) {
328+ mInputProcessor.clearSelection();
329+ setState(STATE_GRID_VIEW);
330+ } else {
331+ disableLocationFiltering();
332+ }
333+ } else {
334+ sHud.setAlpha(1.0f);
335+ }
336+ }
337+ });
338+ }
339+ }
340+
341+ protected void disableLocationFiltering() {
342+ if (mLocationFilter) {
343+ mLocationFilter = false;
344+ mMediaFeed.removeFilter();
345+ sHud.getPathBar().popLabel();
346+ }
347+ }
348+
349+ boolean goBack() {
350+ if (mFeedAboutToChange) {
351+ return false;
352+ }
353+ int state = mState;
354+ if (mInputProcessor.getCurrentSelectedSlot() == Shared.INVALID) {
355+ if (mLocationFilter) {
356+ disableLocationFiltering();
357+ setState(STATE_TIMELINE);
358+ return true;
359+ }
360+ }
361+ switch (state) {
362+ case STATE_GRID_VIEW:
363+ setState(STATE_MEDIA_SETS);
364+ break;
365+ case STATE_TIMELINE:
366+ setState(STATE_GRID_VIEW);
367+ break;
368+ case STATE_FULL_SCREEN:
369+ setState(STATE_GRID_VIEW);
370+ mInputProcessor.clearSelection();
371+ break;
372+ default:
373+ return false;
374+ }
375+ return true;
376+ }
377+
378+ public void endSlideshow() {
379+ mSlideshowMode = false;
380+ if (mWakeLock != null) {
381+ if (mWakeLock.isHeld()) {
382+ mWakeLock.release();
383+ }
384+ mWakeLock = null;
385+ }
386+ sHud.setAlpha(1.0f);
387+ }
388+
389+ @Override
390+ public void onSensorChanged(RenderView view, SensorEvent event) {
391+ mInputProcessor.onSensorChanged(view, event, mState);
392+ }
393+
394+ public DataSource getDataSource() {
395+ if (mMediaFeed != null)
396+ return mMediaFeed.getDataSource();
397+ return null;
398+ }
399+
400+ public void setDataSource(DataSource dataSource) {
401+ MediaFeed feed = mMediaFeed;
402+ if (feed != null) {
403+ feed.shutdown();
404+ sDisplayList.clear();
405+ mBackground.clear();
406+ }
407+ mMediaFeed = new MediaFeed(mContext, dataSource, this);
408+ mMediaFeed.start();
409+ }
410+
411+ public IndexRange getVisibleRange() {
412+ return sVisibleRange;
413+ }
414+
415+ public IndexRange getBufferedVisibleRange() {
416+ return sBufferedVisibleRange;
417+ }
418+
419+ public IndexRange getCompleteRange() {
420+ return sCompleteRange;
421+ }
422+
423+ private int hitTest(Vector3f worldPos, int itemWidth, int itemHeight) {
424+ int retVal = Shared.INVALID;
425+ int firstSlotIndex = 0;
426+ int lastSlotIndex = 0;
427+ IndexRange rangeToUse = sVisibleRange;
428+ synchronized (rangeToUse) {
429+ firstSlotIndex = rangeToUse.begin;
430+ lastSlotIndex = rangeToUse.end;
431+ }
432+ Pool<Vector3f> pool = sTempVec;
433+ float itemWidthBy2 = itemWidth * 0.5f;
434+ float itemHeightBy2 = itemHeight * 0.5f;
435+ Vector3f position = pool.create();
436+ Vector3f deltaAnchorPosition = pool.create();
437+ try {
438+ deltaAnchorPosition.set(sDeltaAnchorPosition);
439+ for (int i = firstSlotIndex; i <= lastSlotIndex; ++i) {
440+ GridCameraManager.getSlotPositionForSlotIndex(i, mCamera, mLayoutInterface, deltaAnchorPosition, position);
441+ if (FloatUtils.boundsContainsPoint(position.x - itemWidthBy2, position.x + itemWidthBy2,
442+ position.y - itemHeightBy2, position.y + itemHeightBy2, worldPos.x, worldPos.y)) {
443+ retVal = i;
444+ break;
445+ }
446+ }
447+ } finally {
448+ pool.delete(deltaAnchorPosition);
449+ pool.delete(position);
450+ }
451+ return retVal;
452+ }
453+
454+ void centerCameraForSlot(int slotIndex, float baseConvergence) {
455+ float imageTheta = 0.0f;
456+ DisplayItem displayItem = getDisplayItemForSlotId(slotIndex);
457+ if (displayItem != null) {
458+ imageTheta = displayItem.getImageTheta();
459+ }
460+ mCameraManager.centerCameraForSlot(mLayoutInterface, slotIndex, baseConvergence, sDeltaAnchorPositionUncommited,
461+ mInputProcessor.getCurrentSelectedSlot(), mZoomValue, imageTheta, mState);
462+ }
463+
464+ boolean constrainCameraForSlot(int slotIndex) {
465+ return mCameraManager.constrainCameraForSlot(mLayoutInterface, slotIndex, sDeltaAnchorPosition, mCurrentFocusItemWidth,
466+ mCurrentFocusItemHeight);
467+ }
468+
469+ // Called on render thread before rendering.
470+ @Override
471+ public boolean update(RenderView view, float timeElapsed) {
472+ if (mFeedAboutToChange == false) {
473+ mTimeElapsedSinceTransition += timeElapsed;
474+ mTimeElapsedSinceGridViewReady += timeElapsed;
475+ if (mTimeElapsedSinceGridViewReady >= 1.0f) {
476+ mTimeElapsedSinceGridViewReady = 1.0f;
477+ }
478+ mTimeElapsedSinceStackViewReady += timeElapsed;
479+ if (mTimeElapsedSinceStackViewReady >= 1.0f) {
480+ mTimeElapsedSinceStackViewReady = 1.0f;
481+ }
482+ } else {
483+ mTimeElapsedSinceTransition = 0;
484+ }
485+ if (mMediaFeed != null && mMediaFeed.isSingleImageMode()) {
486+ HudLayer hud = getHud();
487+ hud.getPathBar().setHidden(true);
488+ hud.getMenuBar().setHidden(true);
489+ if (hud.getMode() != HudLayer.MODE_NORMAL)
490+ hud.setMode(HudLayer.MODE_NORMAL);
491+ }
492+ if (view.elapsedLoadingExpensiveTextures() > 150 || (mMediaFeed != null && mMediaFeed.getWaitingForMediaScanner())) {
493+ sHud.getPathBar().setAnimatedIcons(GridDrawables.TEXTURE_SPINNER);
494+ } else {
495+ sHud.getPathBar().setAnimatedIcons(null);
496+ }
497+
498+ // In that case, we need to commit the respective Display Items when the
499+ // feed was updated.
500+ GridCamera camera = mCamera;
501+ camera.update(timeElapsed);
502+ DisplayItem anchorDisplayItem = getAnchorDisplayItem(ANCHOR_CENTER);
503+ if (anchorDisplayItem != null && !sHud.getTimeBar().isDragged()) {
504+ sHud.getTimeBar().setItem(anchorDisplayItem.mItemRef);
505+ }
506+ sDisplayList.update(timeElapsed);
507+ mInputProcessor.update(timeElapsed);
508+ mSelectedAlpha = FloatUtils.animate(mSelectedAlpha, mTargetAlpha, timeElapsed * 0.5f);
509+ if (mState == STATE_FULL_SCREEN) {
510+ sHud.autoHide(true);
511+ } else {
512+ sHud.autoHide(false);
513+ sHud.setAlpha(1.0f);
514+ }
515+ GridQuad[] fullscreenQuads = GridDrawables.sFullscreenGrid;
516+ int numFullScreenQuads = fullscreenQuads.length;
517+ for (int i = 0; i < numFullScreenQuads; ++i) {
518+ fullscreenQuads[i].update(timeElapsed);
519+ }
520+ if (mSlideshowMode && mState == STATE_FULL_SCREEN) {
521+ mTimeElapsedSinceView += timeElapsed;
522+ if (mTimeElapsedSinceView > SLIDESHOW_TRANSITION_TIME) {
523+ // time to go to the next slide
524+ mTimeElapsedSinceView = 0.0f;
525+ changeFocusToNextSlot(0.5f);
526+ mCamera.commitMoveInX();
527+ mCamera.commitMoveInY();
528+ }
529+ }
530+ if (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) {
531+ mCamera.moveYTo(-0.1f);
532+ mCamera.commitMoveInY();
533+ }
534+ boolean dirty = mDrawManager.update(timeElapsed);
535+ dirty |= mSlideshowMode;
536+ dirty |= mFramesDirty > 0;
537+ ++mFrameCount;
538+ if (mFramesDirty > 0) {
539+ --mFramesDirty;
540+ }
541+ try {
542+ if (mMediaFeed != null && (mMediaFeed.getWaitingForMediaScanner())) {
543+ // We limit the drawing of the frame so that the MediaScanner
544+ // thread can do its work
545+ Thread.sleep(200);
546+ }
547+ } catch (InterruptedException e) {
548+
549+ }
550+ if (sDisplayList.getNumAnimatables() != 0 || mCamera.isAnimating()
551+ || (mTimeElapsedSinceTransition > 0.0f && mTimeElapsedSinceTransition < 1.0f) || mSelectedAlpha != mTargetAlpha
552+ // || (mAnimatedFov != mTargetFov)
553+ || dirty)
554+ return true;
555+ else
556+ return false;
557+ }
558+
559+ private void computeVisibleRange() {
560+ if (mPerformingLayoutChange)
561+ return;
562+ if (sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited) == false) {
563+ sDeltaAnchorPosition.set(sDeltaAnchorPositionUncommited);
564+ }
565+ mCameraManager.computeVisibleRange(mMediaFeed, mLayoutInterface, sDeltaAnchorPosition, sVisibleRange,
566+ sBufferedVisibleRange, sCompleteRange, mState);
567+ }
568+
569+ private void computeVisibleItems() {
570+ if (mFeedAboutToChange == true || mPerformingLayoutChange == true) {
571+ return;
572+ }
573+ computeVisibleRange();
574+ int deltaBegin = sBufferedVisibleRange.begin - sPreviousDataRange.begin;
575+ int deltaEnd = sBufferedVisibleRange.end - sPreviousDataRange.end;
576+ if (deltaBegin != 0 || deltaEnd != 0) {
577+ // The delta has changed, we have to compute the display items
578+ // again.
579+ // We find the intersection range, these slots have not changed at
580+ // all.
581+ int firstVisibleSlotIndex = sBufferedVisibleRange.begin;
582+ int lastVisibleSlotIndex = sBufferedVisibleRange.end;
583+ sPreviousDataRange.begin = firstVisibleSlotIndex;
584+ sPreviousDataRange.end = lastVisibleSlotIndex;
585+
586+ Pool<Vector3f> pool = sTempVec;
587+ Vector3f position = pool.create();
588+ Vector3f deltaAnchorPosition = pool.create();
589+ try {
590+ MediaFeed feed = mMediaFeed;
591+ DisplayList displayList = sDisplayList;
592+ DisplayItem[] displayItems = sDisplayItems;
593+ DisplaySlot[] displaySlots = sDisplaySlots;
594+ int numDisplayItems = displayItems.length;
595+ int numDisplaySlots = displaySlots.length;
596+ ArrayList<MediaItem> visibleItems = sVisibleItems;
597+ deltaAnchorPosition.set(sDeltaAnchorPosition);
598+ LayoutInterface layout = mLayoutInterface;
599+ GridCamera camera = mCamera;
600+ for (int i = firstVisibleSlotIndex; i <= lastVisibleSlotIndex; ++i) {
601+ GridCameraManager.getSlotPositionForSlotIndex(i, camera, layout, deltaAnchorPosition, position);
602+ MediaSet set = feed.getSetForSlot(i);
603+ int indexIntoSlots = i - firstVisibleSlotIndex;
604+
605+ if (set != null && indexIntoSlots >= 0 && indexIntoSlots < numDisplaySlots) {
606+ ArrayList<MediaItem> items = set.getItems();
607+ displaySlots[indexIntoSlots].setMediaSet(set);
608+ ArrayList<MediaItem> bestItems = sTempList;
609+ if (mTimeElapsedSinceTransition < 1.0f) {
610+ // We always show the same top thumbnails for a
611+ // stack of albums
612+ if (mState == STATE_MEDIA_SETS)
613+ ArrayUtils.computeSortedIntersection(items, visibleItems, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
614+ else
615+ ArrayUtils.computeSortedIntersection(visibleItems, items, MAX_ITEMS_PER_SLOT, bestItems, sTempHash);
616+ }
617+
618+ int numItemsInSet = set.getNumItems();
619+ int numBestItems = bestItems.size();
620+ int originallyFoundItems = numBestItems;
621+ if (numBestItems < MAX_ITEMS_PER_SLOT) {
622+ int itemsRemaining = MAX_ITEMS_PER_SLOT - numBestItems;
623+ for (int currItemPos = 0; currItemPos < numItemsInSet; currItemPos++) {
624+ MediaItem item = items.get(currItemPos);
625+ if (mTimeElapsedSinceTransition >= 1.0f || !bestItems.contains(item)) {
626+ bestItems.add(item);
627+ if (--itemsRemaining == 0) {
628+ break;
629+ }
630+ }
631+ }
632+ }
633+ numBestItems = bestItems.size();
634+ int baseIndex = (i - firstVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
635+ for (int j = 0; j < numBestItems; ++j) {
636+ if (baseIndex + j >= numDisplayItems) {
637+ break;
638+ }
639+ if (j >= numItemsInSet) {
640+ displayItems[baseIndex + j] = null;
641+ } else {
642+ MediaItem item = bestItems.get(j);
643+ if (item != null) {
644+ DisplayItem displayItem = displayList.get(item);
645+ if ((mState == STATE_FULL_SCREEN && i != mInputProcessor.getCurrentSelectedSlot())
646+ || (mState == STATE_GRID_VIEW && (mTimeElapsedSinceTransition > 1.0f || j >= originallyFoundItems))) {
647+ displayItem.set(position, j, false);
648+ displayItem.commit();
649+ } else {
650+ displayList.setPositionAndStackIndex(displayItem, position, j, true);
651+ }
652+ displayItems[baseIndex + j] = displayItem;
653+ }
654+ }
655+ }
656+ for (int j = numBestItems; j < MAX_ITEMS_PER_SLOT; ++j) {
657+ displayItems[baseIndex + j] = null;
658+ }
659+ bestItems.clear();
660+ }
661+ }
662+ if (mFeedChanged) {
663+ mFeedChanged = false;
664+ if (mInputProcessor != null && mState == STATE_FULL_SCREEN && mRequestFocusContentUri == null) {
665+ int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
666+ if (currentSelectedSlot > sCompleteRange.end)
667+ currentSelectedSlot = sCompleteRange.end;
668+ mInputProcessor.setCurrentSelectedSlot(currentSelectedSlot);
669+ }
670+ if (mState == STATE_GRID_VIEW) {
671+ MediaSet expandedSet = mMediaFeed.getExpandedMediaSet();
672+ if (expandedSet != null) {
673+ if (!sHud.getPathBar().getCurrentLabel().equals(expandedSet.mNoCountTitleString)) {
674+ sHud.getPathBar().changeLabel(expandedSet.mNoCountTitleString);
675+ }
676+ }
677+ }
678+ if (mRequestFocusContentUri != null) {
679+ // We have to find the item that has this contentUri
680+ if (mState == STATE_FULL_SCREEN) {
681+ int numSlots = sCompleteRange.end + 1;
682+ for (int i = 0; i < numSlots; ++i) {
683+ MediaSet set = feed.getSetForSlot(i);
684+ ArrayList<MediaItem> items = set.getItems();
685+ int numItems = items.size();
686+ for (int j = 0; j < numItems; ++j) {
687+ String itemUri = items.get(j).mContentUri;
688+ if (itemUri != null && mRequestFocusContentUri != null) {
689+ if (itemUri.equals(mRequestFocusContentUri)) {
690+ mInputProcessor.setCurrentSelectedSlot(i);
691+ mRequestFocusContentUri = null;
692+ break;
693+ }
694+ }
695+ }
696+ }
697+ }
698+ }
699+ }
700+ } finally {
701+ pool.delete(position);
702+ pool.delete(deltaAnchorPosition);
703+ }
704+ // We keep upto 400 thumbnails in memory.
705+ int numThumbnailsToKeepInMemory = (mState == STATE_MEDIA_SETS || mState == STATE_TIMELINE) ? 100 : 400;
706+ int startMemoryRange = (sBufferedVisibleRange.begin / numThumbnailsToKeepInMemory) * numThumbnailsToKeepInMemory;
707+ if (mStartMemoryRange != startMemoryRange) {
708+ mStartMemoryRange = startMemoryRange;
709+ clearUnusedThumbnails();
710+ }
711+ }
712+ }
713+
714+ @Override
715+ public void handleLowMemory() {
716+ clearUnusedThumbnails();
717+ GridDrawables.sStringTextureTable.clear();
718+ mBackground.clearCache();
719+ }
720+
721+ // This method can be potentially expensive
722+ public void clearUnusedThumbnails() {
723+ sDisplayList.clearExcept(sDisplayItems);
724+ }
725+
726+ @Override
727+ public void onSurfaceCreated(RenderView view, GL11 gl) {
728+ sDisplayList.clear();
729+ sHud.clear();
730+ sHud.reset();
731+ GridDrawables.sStringTextureTable.clear();
732+ mDrawables.onSurfaceCreated(view, gl);
733+ mBackground.clear();
734+ }
735+
736+ @Override
737+ public void onSurfaceChanged(RenderView view, int width, int height) {
738+ mCamera.viewportChanged(width, height, mCamera.mItemWidth, mCamera.mItemHeight);
739+ view.setFov(mCamera.mFov);
740+ setState(mState);
741+ }
742+
743+ // Renders the node in a given pass.
744+ public void renderOpaque(RenderView view, GL11 gl) {
745+ GridCamera camera = mCamera;
746+ int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
747+ computeVisibleItems();
748+
749+ gl.glMatrixMode(GL11.GL_MODELVIEW);
750+ gl.glLoadIdentity();
751+ GLU.gluLookAt(gl, -camera.mEyeX, -camera.mEyeY, -camera.mEyeZ, -camera.mLookAtX, -camera.mLookAtY, -camera.mLookAtZ,
752+ camera.mUpX, camera.mUpY, camera.mUpZ);
753+ view.setAlpha(1.0f);
754+ if (mSelectedAlpha != 1.0f) {
755+ gl.glEnable(GL11.GL_BLEND);
756+ gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
757+ view.setAlpha(mSelectedAlpha);
758+ }
759+ if (selectedSlotIndex != Shared.INVALID) {
760+ mTargetAlpha = 0.0f;
761+ } else {
762+ mTargetAlpha = 1.0f;
763+ }
764+ mDrawManager.prepareDraw(sBufferedVisibleRange, sVisibleRange, selectedSlotIndex, mInputProcessor.getCurrentFocusSlot(),
765+ mInputProcessor.isFocusItemPressed());
766+ if (mSelectedAlpha != 0.0f) {
767+ mDrawManager.drawThumbnails(view, gl, mState);
768+ }
769+ if (mSelectedAlpha != 1.0f) {
770+ gl.glDisable(GL11.GL_BLEND);
771+ }
772+ // We draw the selected slotIndex.
773+ if (selectedSlotIndex != Shared.INVALID) {
774+ mDrawManager.drawFocusItems(view, gl, mZoomValue, mSlideshowMode, mTimeElapsedSinceView);
775+ mCurrentFocusItemWidth = mDrawManager.getFocusQuadWidth();
776+ mCurrentFocusItemHeight = mDrawManager.getFocusQuadHeight();
777+ }
778+ view.setAlpha(mSelectedAlpha);
779+ }
780+
781+ public void renderBlended(RenderView view, GL11 gl) {
782+ // We draw the placeholder for all visible slots.
783+ if (sHud != null && mDrawManager != null) {
784+ mDrawManager.drawBlendedComponents(view, gl, mSelectedAlpha, mState, sHud.getMode(), mTimeElapsedSinceStackViewReady,
785+ mTimeElapsedSinceGridViewReady, sBucketList, mMediaFeed.getWaitingForMediaScanner() || mFeedAboutToChange
786+ || mMediaFeed.isLoading());
787+ }
788+ }
789+
790+ public synchronized void onLayout(int newAnchorSlotIndex, int currentAnchorSlotIndex, LayoutInterface oldLayout) {
791+ if (mPerformingLayoutChange || !sDeltaAnchorPosition.equals(sDeltaAnchorPositionUncommited)) {
792+ return;
793+ }
794+
795+ mTimeElapsedSinceTransition = 0.0f;
796+ mPerformingLayoutChange = true;
797+ LayoutInterface layout = mLayoutInterface;
798+ if (oldLayout == null) {
799+ oldLayout = sfullScreenLayoutInterface;
800+ }
801+ GridCamera camera = mCamera;
802+ if (currentAnchorSlotIndex == Shared.INVALID) {
803+ currentAnchorSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
804+ if (mCurrentExpandedSlot != Shared.INVALID) {
805+ currentAnchorSlotIndex = mCurrentExpandedSlot;
806+ }
807+ int selectedSlotIndex = mInputProcessor.getCurrentSelectedSlot();
808+ if (selectedSlotIndex != Shared.INVALID) {
809+ currentAnchorSlotIndex = selectedSlotIndex;
810+ }
811+ }
812+ if (newAnchorSlotIndex == Shared.INVALID) {
813+ newAnchorSlotIndex = currentAnchorSlotIndex;
814+ }
815+ int itemHeight = camera.mItemHeight;
816+ int itemWidth = camera.mItemWidth;
817+ Pool<Vector3f> pool = sTempVec;
818+ Vector3f deltaAnchorPosition = pool.create();
819+ Vector3f currentSlotPosition = pool.create();
820+ try {
821+ deltaAnchorPosition.set(0, 0, 0);
822+ if (currentAnchorSlotIndex != Shared.INVALID && newAnchorSlotIndex != Shared.INVALID) {
823+ layout.getPositionForSlotIndex(newAnchorSlotIndex, itemWidth, itemHeight, deltaAnchorPosition);
824+ oldLayout.getPositionForSlotIndex(currentAnchorSlotIndex, itemWidth, itemHeight, currentSlotPosition);
825+ currentSlotPosition.subtract(sDeltaAnchorPosition);
826+ deltaAnchorPosition.subtract(currentSlotPosition);
827+ deltaAnchorPosition.y = 0;
828+ deltaAnchorPosition.z = 0;
829+ }
830+ sDeltaAnchorPositionUncommited.set(deltaAnchorPosition);
831+ } finally {
832+ pool.delete(deltaAnchorPosition);
833+ pool.delete(currentSlotPosition);
834+ }
835+ centerCameraForSlot(newAnchorSlotIndex, 1.0f);
836+ mCurrentExpandedSlot = Shared.INVALID;
837+
838+ // Force recompute of visible items and their positions.
839+ ((GridLayoutInterface) oldLayout).mNumRows = ((GridLayoutInterface) layout).mNumRows;
840+ ((GridLayoutInterface) oldLayout).mSpacingX = ((GridLayoutInterface) layout).mSpacingX;
841+ ((GridLayoutInterface) oldLayout).mSpacingY = ((GridLayoutInterface) layout).mSpacingY;
842+ forceRecomputeVisibleRange();
843+ mPerformingLayoutChange = false;
844+ }
845+
846+ private void forceRecomputeVisibleRange() {
847+ sPreviousDataRange.begin = Shared.INVALID;
848+ sPreviousDataRange.end = Shared.INVALID;
849+ if (mView != null) {
850+ mView.requestRender();
851+ }
852+ }
853+
854+ // called on background thread
855+ public synchronized void onFeedChanged(MediaFeed feed, boolean needsLayout) {
856+ if (!needsLayout) {
857+ mFeedChanged = true;
858+ forceRecomputeVisibleRange();
859+ if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
860+ sHud.setFeed(feed, mState, needsLayout);
861+ return;
862+ }
863+
864+ while (mPerformingLayoutChange == true) {
865+ Thread.yield();
866+ }
867+ if (mState == STATE_GRID_VIEW) {
868+ if (sHud != null) {
869+ MediaSet set = feed.getCurrentSet();
870+ if (set != null && !mLocationFilter)
871+ sHud.getPathBar().changeLabel(set.mNoCountTitleString);
872+ }
873+ }
874+ DisplayItem[] displayItems = sDisplayItems;
875+ int firstBufferedVisibleSlotIndex = sBufferedVisibleRange.begin;
876+ int lastBufferedVisibleSlotIndex = sBufferedVisibleRange.end;
877+ int currentlyVisibleSlotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
878+ if (mCurrentExpandedSlot != Shared.INVALID) {
879+ currentlyVisibleSlotIndex = mCurrentExpandedSlot;
880+ }
881+ MediaItem anchorItem = null;
882+ ArrayList<MediaItem> visibleItems = sVisibleItems;
883+ visibleItems.clear();
884+ visibleItems.ensureCapacity(lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex);
885+ if (currentlyVisibleSlotIndex != Shared.INVALID && currentlyVisibleSlotIndex >= firstBufferedVisibleSlotIndex
886+ && currentlyVisibleSlotIndex <= lastBufferedVisibleSlotIndex) {
887+ int baseIndex = (currentlyVisibleSlotIndex - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
888+ for (int i = 0; i < MAX_ITEMS_PER_SLOT; ++i) {
889+ DisplayItem displayItem = displayItems[baseIndex + i];
890+ if (displayItem != null) {
891+ if (anchorItem == null) {
892+ anchorItem = displayItem.mItemRef;
893+ }
894+ visibleItems.add(displayItem.mItemRef);
895+ }
896+ }
897+ }
898+ // We want to add items from the middle.
899+ int numItems = lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex + 1;
900+ int midPoint = (lastBufferedVisibleSlotIndex - firstBufferedVisibleSlotIndex) / 2;
901+ int length = displayItems.length;
902+ for (int i = 0; i < numItems; ++i) {
903+ int index = midPoint + Shared.midPointIterator(i);
904+ int indexIntoDisplayItem = (index - firstBufferedVisibleSlotIndex) * MAX_ITEMS_PER_SLOT;
905+ if (indexIntoDisplayItem >= 0 && indexIntoDisplayItem < length) {
906+ for (int j = 0; j < MAX_ITEMS_PER_SLOT; ++j) {
907+ DisplayItem displayItem = displayItems[indexIntoDisplayItem + j];
908+ if (displayItem != null) {
909+ MediaItem item = displayItem.mItemRef;
910+ if (item != anchorItem) {
911+ visibleItems.add(item);
912+ }
913+ }
914+ }
915+ }
916+ }
917+ int newSlotIndex = Shared.INVALID;
918+ if (anchorItem != null) {
919+ // We try to find the anchor item in the new feed.
920+ int numSlots = feed.getNumSlots();
921+ for (int i = 0; i < numSlots; ++i) {
922+ MediaSet set = feed.getSetForSlot(i);
923+ if (set != null && set.containsItem(anchorItem)) {
924+ newSlotIndex = i;
925+ break;
926+ }
927+ }
928+ }
929+
930+ if (anchorItem != null && newSlotIndex == Shared.INVALID) {
931+ int numSlots = feed.getNumSlots();
932+ MediaSet parentSet = anchorItem.mParentMediaSet;
933+ for (int i = 0; i < numSlots; ++i) {
934+ MediaSet set = feed.getSetForSlot(i);
935+ if (set != null && set.mId == parentSet.mId) {
936+ newSlotIndex = i;
937+ break;
938+ }
939+ }
940+ }
941+
942+ // We must create a new display store now since the data has changed.
943+ if (newSlotIndex != Shared.INVALID) {
944+ if (mState == STATE_MEDIA_SETS) {
945+ sDisplayList.clearExcept(displayItems);
946+ }
947+ onLayout(newSlotIndex, currentlyVisibleSlotIndex, null);
948+ } else {
949+ forceRecomputeVisibleRange();
950+ }
951+ mCurrentExpandedSlot = Shared.INVALID;
952+ mFeedAboutToChange = false;
953+ mFeedChanged = true;
954+ if (feed != null) {
955+ if (mState == STATE_GRID_VIEW || mState == STATE_FULL_SCREEN)
956+ sHud.setFeed(feed, mState, needsLayout);
957+ }
958+ if (mView != null) {
959+ mView.requestRender();
960+ }
961+ }
962+
963+ public DisplayItem getRepresentativeDisplayItem() {
964+ int slotIndex = Shared.INVALID;
965+ if (mInputProcessor != null) {
966+ slotIndex = mInputProcessor.getCurrentFocusSlot();
967+ }
968+ if (slotIndex == Shared.INVALID) {
969+ slotIndex = getAnchorSlotIndex(ANCHOR_CENTER);
970+ }
971+ int index = (slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT;
972+ if (index >= 0 && index < MAX_ITEMS_DRAWABLE) {
973+ return sDisplayItems[index];
974+ } else {
975+ return null;
976+ }
977+ }
978+
979+ public DisplayItem getAnchorDisplayItem(int type) {
980+ int slotIndex = getAnchorSlotIndex(type);
981+ return sDisplayItems[(slotIndex - sBufferedVisibleRange.begin) * MAX_ITEMS_PER_SLOT];
982+ }
983+
984+ public float getScrollPosition() {
985+ return (mCamera.mLookAtX * mCamera.mScale + sDeltaAnchorPosition.x); // in
986+ // pixels
987+ }
988+
989+ public DisplayItem getDisplayItemForScrollPosition(float posX) {
990+ Pool<Vector3f> pool = sTempVecAlt;
991+ MediaFeed feed = mMediaFeed;
992+ int itemWidth = mCamera.mItemWidth;
993+ int itemHeight = mCamera.mItemHeight;
994+ GridLayoutInterface gridInterface = (GridLayoutInterface) mLayoutInterface;
995+ float absolutePosX = posX;
996+ int left = (int) ((absolutePosX / itemWidth) * gridInterface.mNumRows);
997+ int right = feed == null ? 0 : (int) (feed.getNumSlots());
998+ int retSlot = left;
999+ Vector3f position = pool.create();
1000+ try {
1001+ for (int i = left; i < right; ++i) {
1002+ gridInterface.getPositionForSlotIndex(i, itemWidth, itemHeight, position);
1003+ retSlot = i;
1004+ if (position.x >= absolutePosX) {
1005+ break;
1006+ }
1007+ }
1008+ } finally {
1009+ pool.delete(position);
1010+ }
1011+ if (mFeedAboutToChange) {
1012+ return null;
1013+ }
1014+ right = feed == null ? 0 : feed.getNumSlots();
1015+ if (right == 0) {
1016+ return null;
1017+ }
1018+
1019+ if (retSlot >= right)
1020+ retSlot = right - 1;
1021+ MediaSet set = feed.getSetForSlot(retSlot);
1022+ if (set != null) {
1023+ ArrayList<MediaItem> items = set.getItems();
1024+ if (items != null && set.getNumItems() > 0) {
1025+ return (sDisplayList.get(items.get(0)));
1026+ }
1027+ }
1028+ return null;
1029+ }
1030+
1031+ // Returns the top left-most item.
1032+ public int getAnchorSlotIndex(int anchorType) {
1033+ int retVal = 0;
1034+ switch (anchorType) {
1035+ case ANCHOR_LEFT:
1036+ retVal = sVisibleRange.begin;
1037+ break;
1038+ case ANCHOR_RIGHT:
1039+ retVal = sVisibleRange.end;
1040+ break;
1041+ case ANCHOR_CENTER:
1042+ retVal = (sVisibleRange.begin + sVisibleRange.end) / 2;
1043+ break;
1044+ }
1045+ return retVal;
1046+ }
1047+
1048+ DisplayItem getDisplayItemForSlotId(int slotId) {
1049+ int index = slotId - sBufferedVisibleRange.begin;
1050+ if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
1051+ return sDisplayItems[index * MAX_ITEMS_PER_SLOT];
1052+ }
1053+ return null;
1054+ }
1055+
1056+ boolean changeFocusToNextSlot(float convergence) {
1057+ int currentSelectedSlot = mInputProcessor.getCurrentSelectedSlot();
1058+ boolean retVal = changeFocusToSlot(currentSelectedSlot + 1, convergence);
1059+ if (mInputProcessor.getCurrentSelectedSlot() == currentSelectedSlot) {
1060+ endSlideshow();
1061+ sHud.setAlpha(1.0f);
1062+ }
1063+ return retVal;
1064+ }
1065+
1066+ boolean changeFocusToSlot(int slotId, float convergence) {
1067+ mZoomValue = 1.0f;
1068+ int index = slotId - sBufferedVisibleRange.begin;
1069+ if (index >= 0 && slotId <= sBufferedVisibleRange.end) {
1070+ DisplayItem displayItem = sDisplayItems[index * MAX_ITEMS_PER_SLOT];
1071+ if (displayItem != null) {
1072+ MediaItem item = displayItem.mItemRef;
1073+ sHud.fullscreenSelectionChanged(item, slotId + 1, sCompleteRange.end + 1);
1074+ if (slotId != Shared.INVALID && slotId <= sCompleteRange.end) {
1075+ mInputProcessor.setCurrentFocusSlot(slotId);
1076+ centerCameraForSlot(slotId, convergence);
1077+ return true;
1078+ } else {
1079+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), convergence);
1080+ return false;
1081+ }
1082+ }
1083+ }
1084+ return false;
1085+ }
1086+
1087+ boolean changeFocusToPreviousSlot(float convergence) {
1088+ return changeFocusToSlot(mInputProcessor.getCurrentSelectedSlot() - 1, convergence);
1089+ }
1090+
1091+ public ArrayList<MediaBucket> getSelectedBuckets() {
1092+ return sBucketList.get();
1093+ }
1094+
1095+ public void selectAll() {
1096+ if (mState != STATE_FULL_SCREEN) {
1097+ int numSlots = sCompleteRange.end + 1;
1098+ for (int i = 0; i < numSlots; ++i) {
1099+ addSlotToSelectedItems(i, false, false);
1100+ }
1101+ updateCountOfSelectedItems();
1102+ } else {
1103+ addSlotToSelectedItems(mInputProcessor.getCurrentFocusSlot(), false, true);
1104+ }
1105+ }
1106+
1107+ public void deselectOrCancelSelectMode() {
1108+ if (sBucketList.size() == 0) {
1109+ sHud.cancelSelection();
1110+ } else {
1111+ sBucketList.clear();
1112+ updateCountOfSelectedItems();
1113+ }
1114+ }
1115+
1116+ public void deselectAll() {
1117+ sHud.cancelSelection();
1118+ sBucketList.clear();
1119+ updateCountOfSelectedItems();
1120+ }
1121+
1122+ public void deleteSelection() {
1123+ // Delete the selection and exit selection mode.
1124+ mMediaFeed.performOperation(MediaFeed.OPERATION_DELETE, getSelectedBuckets(), null);
1125+ deselectAll();
1126+
1127+ // If the current set is now empty, return to the parent set.
1128+ if (sCompleteRange.isEmpty()) {
1129+ goBack(); // TODO(venkat): This does not work most of the time, can
1130+ // you take a look?
1131+ }
1132+ }
1133+
1134+ void addSlotToSelectedItems(int slotId, boolean removeIfAlreadyAdded, boolean updateCount) {
1135+ if (mFeedAboutToChange == false) {
1136+ MediaFeed feed = mMediaFeed;
1137+ sBucketList.add(slotId, feed, removeIfAlreadyAdded);
1138+ if (updateCount) {
1139+ updateCountOfSelectedItems();
1140+ if (sBucketList.size() == 0)
1141+ deselectAll();
1142+ }
1143+ }
1144+ sHud.computeBottomMenu();
1145+ }
1146+
1147+ private void updateCountOfSelectedItems() {
1148+ sHud.updateNumItemsSelected(sBucketList.size());
1149+ }
1150+
1151+ public int getMetadataSlotIndexForScreenPosition(int posX, int posY) {
1152+ return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth + (int) (100 * Gallery.PIXEL_DENSITY), mCamera.mItemHeight
1153+ + (int) (100 * Gallery.PIXEL_DENSITY));
1154+ }
1155+
1156+ public int getSlotIndexForScreenPosition(int posX, int posY) {
1157+ return getSlotForScreenPosition(posX, posY, mCamera.mItemWidth, mCamera.mItemHeight);
1158+ }
1159+
1160+ private int getSlotForScreenPosition(int posX, int posY, int itemWidth, int itemHeight) {
1161+ Pool<Vector3f> pool = sTempVec;
1162+ int retVal = 0;
1163+ Vector3f worldPos = pool.create();
1164+ try {
1165+ GridCamera camera = mCamera;
1166+ camera.convertToCameraSpace(posX, posY, 0, worldPos);
1167+ // slots are expressed in pixels as well
1168+ worldPos.x *= camera.mScale;
1169+ worldPos.y *= camera.mScale;
1170+ // we ignore z
1171+ retVal = hitTest(worldPos, itemWidth, itemHeight);
1172+ } finally {
1173+ pool.delete(worldPos);
1174+ }
1175+ return retVal;
1176+ }
1177+
1178+ public boolean tapGesture(int slotIndex, boolean metadata) {
1179+ MediaFeed feed = mMediaFeed;
1180+ if (!feed.isClustered()) {
1181+ // It is not clustering.
1182+ if (!feed.hasExpandedMediaSet()) {
1183+ if (feed.canExpandSet(slotIndex)) {
1184+ mCurrentExpandedSlot = slotIndex;
1185+ feed.expandMediaSet(slotIndex);
1186+ setState(STATE_GRID_VIEW);
1187+ }
1188+ return false;
1189+ } else {
1190+ return true;
1191+ }
1192+ } else {
1193+ // Select a cluster, and recompute a new cluster within this
1194+ // cluster.
1195+ mCurrentExpandedSlot = slotIndex;
1196+ goBack();
1197+ if (metadata) {
1198+ DisplaySlot slot = sDisplaySlots[slotIndex - sBufferedVisibleRange.begin];
1199+ if (slot.hasValidLocation()) {
1200+ MediaSet set = slot.getMediaSet();
1201+ if (set.mReverseGeocodedLocation != null) {
1202+ enableLocationFiltering(set.mReverseGeocodedLocation);
1203+ }
1204+ feed.setFilter(new LocationMediaFilter(set.mMinLatLatitude, set.mMinLonLongitude, set.mMaxLatLatitude,
1205+ set.mMaxLonLongitude));
1206+ }
1207+ }
1208+ return false;
1209+ }
1210+ }
1211+
1212+ public void onTimeChanged(TimeBar timebar) {
1213+ if (mFeedAboutToChange) {
1214+ return;
1215+ }
1216+ // TODO lot of optimization possible here
1217+ MediaItem item = timebar.getItem();
1218+ MediaFeed feed = mMediaFeed;
1219+ int numSlots = feed.getNumSlots();
1220+ for (int i = 0; i < numSlots; ++i) {
1221+ MediaSet set = feed.getSetForSlot(i);
1222+ if (set == null) {
1223+ return;
1224+ }
1225+ ArrayList<MediaItem> items = set.getItems();
1226+ if (items == null || set.getNumItems() == 0) {
1227+ return;
1228+ }
1229+ if (items.contains(item)) {
1230+ centerCameraForSlot(i, 1.0f);
1231+ break;
1232+ }
1233+ }
1234+ }
1235+
1236+ public void onFeedAboutToChange(MediaFeed feed) {
1237+ mFeedAboutToChange = true;
1238+ mTimeElapsedSinceTransition = 0;
1239+ }
1240+
1241+ public void startSlideshow() {
1242+ endSlideshow();
1243+ mSlideshowMode = true;
1244+ mZoomValue = 1.0f;
1245+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1246+ mTimeElapsedSinceView = SLIDESHOW_TRANSITION_TIME - 1.0f;
1247+ sHud.setAlpha(0);
1248+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
1249+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "GridView.Slideshow");
1250+ mWakeLock.acquire();
1251+ }
1252+
1253+ public void enterSelectionMode() {
1254+ mSlideshowMode = false;
1255+ sHud.enterSelectionMode();
1256+ int currentSlot = mInputProcessor.getCurrentSelectedSlot();
1257+ if (currentSlot == Shared.INVALID) {
1258+ currentSlot = mInputProcessor.getCurrentFocusSlot();
1259+ }
1260+ addSlotToSelectedItems(currentSlot, false, true);
1261+ }
1262+
1263+ private float getFillScreenZoomValue() {
1264+ return GridCameraManager.getFillScreenZoomValue(mCamera, sTempVec, mCurrentFocusItemWidth, mCurrentFocusItemHeight);
1265+ }
1266+
1267+ public void zoomInToSelectedItem() {
1268+ mSlideshowMode = false;
1269+ float potentialZoomValue = getFillScreenZoomValue();
1270+ if (mZoomValue < potentialZoomValue) {
1271+ mZoomValue = potentialZoomValue;
1272+ } else {
1273+ mZoomValue *= 3.0f;
1274+ }
1275+ if (mZoomValue > 6.0f) {
1276+ mZoomValue = 6.0f;
1277+ }
1278+ sHud.setAlpha(1.0f);
1279+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1280+ }
1281+
1282+ public void zoomOutFromSelectedItem() {
1283+ mSlideshowMode = false;
1284+ if (mZoomValue == getFillScreenZoomValue()) {
1285+ mZoomValue = 1.0f;
1286+ } else {
1287+ mZoomValue /= 3.0f;
1288+ }
1289+ if (mZoomValue < 1.0f) {
1290+ mZoomValue = 1.0f;
1291+ }
1292+ sHud.setAlpha(1.0f);
1293+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1294+ }
1295+
1296+ public void rotateSelectedItems(float f) {
1297+ MediaBucketList bucketList = sBucketList;
1298+ ArrayList<MediaBucket> mediaBuckets = bucketList.get();
1299+ DisplayList displayList = sDisplayList;
1300+ int numBuckets = mediaBuckets.size();
1301+ for (int i = 0; i < numBuckets; ++i) {
1302+ MediaBucket bucket = mediaBuckets.get(i);
1303+ ArrayList<MediaItem> mediaItems = bucket.mediaItems;
1304+ if (mediaItems != null) {
1305+ int numMediaItems = mediaItems.size();
1306+ for (int j = 0; j < numMediaItems; ++j) {
1307+ MediaItem item = mediaItems.get(j);
1308+ DisplayItem displayItem = displayList.get(item);
1309+ displayItem.rotateImageBy(f);
1310+ displayList.addToAnimatables(displayItem);
1311+ }
1312+ }
1313+ }
1314+ if (mState == STATE_FULL_SCREEN) {
1315+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1316+ }
1317+ mMediaFeed.performOperation(MediaFeed.OPERATION_ROTATE, mediaBuckets, new Float(f));
1318+ // we recreate these displayitems from the cache
1319+ }
1320+
1321+ public void cropSelectedItem() {
1322+
1323+ }
1324+
1325+ @Override
1326+ public boolean onTouchEvent(MotionEvent event) {
1327+ return mInputProcessor.onTouchEvent(event);
1328+ }
1329+
1330+ @Override
1331+ public boolean onKeyDown(int keyCode, KeyEvent event) {
1332+ if (mInputProcessor != null)
1333+ return mInputProcessor.onKeyDown(keyCode, event, mState);
1334+ return false;
1335+ }
1336+
1337+ public boolean inSlideShowMode() {
1338+ return mSlideshowMode;
1339+ }
1340+
1341+ public boolean noDeleteMode() {
1342+ return mNoDeleteMode || (mMediaFeed != null && mMediaFeed.isSingleImageMode());
1343+ }
1344+
1345+ public float getZoomValue() {
1346+ return mZoomValue;
1347+ }
1348+
1349+ public boolean feedAboutToChange() {
1350+ return mFeedAboutToChange;
1351+ }
1352+
1353+ public boolean isInAlbumMode() {
1354+ return mInAlbum;
1355+ }
1356+
1357+ public Vector3f getDeltaAnchorPosition() {
1358+ return sDeltaAnchorPosition;
1359+ }
1360+
1361+ public int getExpandedSlot() {
1362+ return mCurrentExpandedSlot;
1363+ }
1364+
1365+ public GridLayoutInterface getLayoutInterface() {
1366+ return (GridLayoutInterface) mLayoutInterface;
1367+ }
1368+
1369+ public void setZoomValue(float f) {
1370+ mZoomValue = f;
1371+ centerCameraForSlot(mInputProcessor.getCurrentSelectedSlot(), 1.0f);
1372+ }
1373+
1374+ public void setPickIntent(boolean b) {
1375+ mPickIntent = b;
1376+ sHud.getPathBar().popLabel();
1377+ sHud.getPathBar().pushLabel(R.drawable.icon_location_small, mContext.getResources().getString(R.string.pick),
1378+ new Runnable() {
1379+ public void run() {
1380+ if (sHud.getAlpha() == 1.0f) {
1381+ if (!mFeedAboutToChange) {
1382+ setState(STATE_MEDIA_SETS);
1383+ }
1384+ } else {
1385+ sHud.setAlpha(1.0f);
1386+ }
1387+ }
1388+ });
1389+ }
1390+
1391+ public boolean getPickIntent() {
1392+ return mPickIntent;
1393+ }
1394+
1395+ public void setViewIntent(boolean b, final String setName) {
1396+ mViewIntent = b;
1397+ if (b) {
1398+ mMediaFeed.expandMediaSet(0);
1399+ setState(STATE_GRID_VIEW);
1400+ // We need to make sure we haven't pushed the same label twice
1401+ if (sHud.getPathBar().getNumLevels() == 1) {
1402+ sHud.getPathBar().pushLabel(R.drawable.icon_folder_small, setName, new Runnable() {
1403+ public void run() {
1404+ if (mFeedAboutToChange) {
1405+ return;
1406+ }
1407+ if (sHud.getAlpha() == 1.0f) {
1408+ disableLocationFiltering();
1409+ if (mInputProcessor != null)
1410+ mInputProcessor.clearSelection();
1411+ setState(STATE_GRID_VIEW);
1412+ } else {
1413+ sHud.setAlpha(1.0f);
1414+ }
1415+ }
1416+ });
1417+ }
1418+ }
1419+ }
1420+
1421+ public boolean getViewIntent() {
1422+ return mViewIntent;
1423+ }
1424+
1425+ public void setSingleImage(boolean noDeleteMode) {
1426+ mNoDeleteMode = noDeleteMode;
1427+ mInputProcessor.setCurrentSelectedSlot(0);
1428+ }
1429+
1430+ public MediaFeed getFeed() {
1431+ return mMediaFeed;
1432+ }
1433+
1434+ public void markDirty(int numFrames) {
1435+ mFramesDirty = numFrames;
1436+ }
1437+
1438+ public void focusItem(String contentUri) {
1439+ mRequestFocusContentUri = contentUri;
1440+ mMediaFeed.updateListener(false);
1441+ }
14311442
14321443 }
--- a/src/com/cooliris/media/HudLayer.java
+++ b/src/com/cooliris/media/HudLayer.java
@@ -396,6 +396,7 @@ public final class HudLayer extends Layer {
396396
397397 void setGridLayer(GridLayer layer) {
398398 mGridLayer = layer;
399+ updateViews();
399400 }
400401
401402 int getMode() {
@@ -754,7 +755,6 @@ public final class HudLayer extends Layer {
754755 public void enterSelectionMode() {
755756 setAlpha(1.0f);
756757 setMode(HudLayer.MODE_SELECT);
757-
758758 // if we are in single view mode, show the bottom menu without the delete button.
759759 if (mGridLayer.noDeleteMode()) {
760760 mSelectionMenuBottom.setMenus(mSingleViewIntentBottomMenu);
@@ -762,6 +762,22 @@ public final class HudLayer extends Layer {
762762 mSelectionMenuBottom.setMenus(mNormalBottomMenu);
763763 }
764764 }
765+
766+ public void computeBottomMenu() {
767+ // we need to the same for picasa albums
768+ ArrayList<MediaBucket> selection = mGridLayer.getSelectedBuckets();
769+ Menu[] menus = mSelectionMenuBottom.getMenus();
770+ if (menus == mSingleViewIntentBottomMenu)
771+ return;
772+ int numBuckets = selection.size();
773+ for (int i = 0; i < numBuckets; ++i) {
774+ MediaBucket bucket = selection.get(i);
775+ if (bucket.mediaSet.mPicasaAlbumId != Shared.INVALID) {
776+ mSelectionMenuBottom.setMenus(mSingleViewIntentBottomMenu);
777+ break;
778+ }
779+ }
780+ }
765781
766782 public Layer getMenuBar() {
767783 return mFullscreenMenu;
--- a/src/com/cooliris/media/LocalDataSource.java
+++ b/src/com/cooliris/media/LocalDataSource.java
@@ -32,6 +32,8 @@ public final class LocalDataSource implements DataSource {
3232 public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING;
3333 public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
3434 public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME);
35+
36+ public static boolean sObserverActive = false;
3537 private boolean mDisableImages;
3638 private boolean mDisableVideos;
3739
@@ -43,8 +45,7 @@ public final class LocalDataSource implements DataSource {
4345 }
4446
4547 private Context mContext;
46- private ContentObserver mImagesObserver;
47- private ContentObserver mVideosObserver;
48+ private ContentObserver mObserver;
4849
4950 public LocalDataSource(Context context) {
5051 mContext = context;
@@ -62,35 +63,29 @@ public final class LocalDataSource implements DataSource {
6263 stopListeners();
6364 CacheService.loadMediaSets(feed, this, !mDisableImages, !mDisableVideos);
6465 Handler handler = ((Gallery) mContext).getHandler();
65- ContentObserver imagesObserver = new ContentObserver(handler) {
66- public void onChange(boolean selfChange) {
67- if (((Gallery) mContext).isPaused()) {
68- refresh(feed, CAMERA_BUCKET_ID);
69- refresh(feed, DOWNLOAD_BUCKET_ID);
70-
71- MediaSet set = feed.getCurrentSet();
72- if (set != null && set.mPicasaAlbumId == Shared.INVALID) {
73- refresh(feed, set.mId);
74- }
75- }
76- }
77- };
78- ContentObserver videosObserver = new ContentObserver(handler) {
66+ ContentObserver observer = new ContentObserver(handler) {
7967 public void onChange(boolean selfChange) {
80- if (((Gallery) mContext).isPaused()) {
81- refresh(feed, CAMERA_BUCKET_ID);
82- }
68+ CacheService.senseDirty(mContext, new CacheService.Observer() {
69+ public void onChange(long[] ids) {
70+ if (ids != null) {
71+ int numLongs = ids.length;
72+ for (int i = 0; i < numLongs; ++i) {
73+ refreshUI(feed, ids[i]);
74+ }
75+ }
76+ }
77+ });
8378 }
8479 };
85-
86- // Start listening. TODO: coalesce update notifications while mediascanner is active.
80+
81+ // Start listening.
8782 Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
8883 Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
8984 ContentResolver cr = mContext.getContentResolver();
90- mImagesObserver = imagesObserver;
91- mVideosObserver = videosObserver;
92- cr.registerContentObserver(uriImages, false, mImagesObserver);
93- cr.registerContentObserver(uriVideos, false, mVideosObserver);
85+ mObserver = observer;
86+ cr.registerContentObserver(uriImages, false, observer);
87+ cr.registerContentObserver(uriVideos, false, observer);
88+ sObserverActive = true;
9489 }
9590
9691 public void shutdown() {
@@ -101,23 +96,18 @@ public final class LocalDataSource implements DataSource {
10196
10297 private void stopListeners() {
10398 ContentResolver cr = mContext.getContentResolver();
104- if (mImagesObserver != null) {
105- cr.unregisterContentObserver(mImagesObserver);
106- }
107- if (mVideosObserver != null) {
108- cr.unregisterContentObserver(mVideosObserver);
99+ if (mObserver != null) {
100+ cr.unregisterContentObserver(mObserver);
109101 }
102+ sObserverActive = false;
110103 }
111-
112- protected void refresh(MediaFeed feed, long setIdToUse) {
104+
105+ protected void refreshUI(MediaFeed feed, long setIdToUse) {
113106 if (setIdToUse == Shared.INVALID) {
114107 return;
115108 }
116109 Log.i(TAG, "Refreshing local data source");
117- Gallery.NEEDS_REFRESH = true;
118110 if (feed.getMediaSet(setIdToUse) == null) {
119- if (!CacheService.setHasItems(mContext.getContentResolver(), setIdToUse))
120- return;
121111 MediaSet mediaSet = feed.addMediaSet(setIdToUse, this);
122112 if (setIdToUse == CAMERA_BUCKET_ID) {
123113 mediaSet.mName = CAMERA_STRING;
@@ -125,8 +115,6 @@ public final class LocalDataSource implements DataSource {
125115 mediaSet.mName = DOWNLOAD_STRING;
126116 }
127117 mediaSet.generateTitle(true);
128- if (!CacheService.isPresentInCache(setIdToUse))
129- CacheService.markDirty(mContext);
130118 } else {
131119 MediaSet mediaSet = feed.replaceMediaSet(setIdToUse, this);
132120 if (setIdToUse == CAMERA_BUCKET_ID) {
@@ -135,7 +123,6 @@ public final class LocalDataSource implements DataSource {
135123 mediaSet.mName = DOWNLOAD_STRING;
136124 }
137125 mediaSet.generateTitle(true);
138- CacheService.markDirty(mContext, setIdToUse);
139126 }
140127 }
141128
@@ -152,7 +139,7 @@ public final class LocalDataSource implements DataSource {
152139 return;
153140 }
154141 CacheService.loadMediaItemsIntoMediaFeed(mediaFeed, set, rangeStart, rangeEnd, !mDisableImages, !mDisableVideos);
155- if (set.mId == CAMERA_BUCKET_ID && set.mNumItemsLoaded > 0) {
142+ if (set.mId == CAMERA_BUCKET_ID) {
156143 mediaFeed.moveSetToFront(set);
157144 }
158145 }
--- a/src/com/cooliris/media/MediaBucketList.java
+++ b/src/com/cooliris/media/MediaBucketList.java
@@ -3,6 +3,7 @@ package com.cooliris.media;
33 import java.util.ArrayList;
44 import java.util.HashMap;
55
6+
67 public final class MediaBucketList {
78 private static final Boolean TRUE = new Boolean(true);
89 private static final Boolean FALSE = new Boolean(false);
--- a/src/com/cooliris/media/PicasaDataSource.java
+++ b/src/com/cooliris/media/PicasaDataSource.java
@@ -30,14 +30,31 @@ public final class PicasaDataSource implements DataSource {
3030 private ContentProviderClient mProviderClient;
3131 private final Context mContext;
3232 private ContentObserver mAlbumObserver;
33- private HashMap<String, Boolean> mAccountEnabled = new HashMap<String, Boolean>();
3433
35- public PicasaDataSource(Context context) {
34+ public PicasaDataSource(final Context context) {
3635 mContext = context;
3736 }
37+
38+ public static final HashMap<String, Boolean> getAccountStatus(final Context context) {
39+ final Account[] accounts = PicasaApi.getAccounts(context);
40+ int numAccounts = accounts.length;
41+ HashMap<String, Boolean> accountsEnabled = new HashMap<String, Boolean>(numAccounts);
42+ for (int i = 0; i < numAccounts; ++i) {
43+ Account account = accounts[i];
44+ boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
45+ String username = account.name;
46+ if (username.contains("@gmail.") || username.contains("@googlemail.")) {
47+ // Strip the domain from GMail accounts for canonicalization. TODO: is there an official way?
48+ username = username.substring(0, username.indexOf('@'));
49+ }
50+ accountsEnabled.put(username, new Boolean(isEnabled));
51+ }
52+ return accountsEnabled;
53+ }
3854
3955 public void loadMediaSets(final MediaFeed feed) {
40- if (mProviderClient == null) {
56+ // We do this here and not in the constructor to speed application loading time since this method is called in a background thread
57+ if (mProviderClient == null) {
4158 mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
4259 }
4360 // 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 {
4562
4663 // Ensure that users are up to date. TODO: also listen for accounts changed broadcast.
4764 PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0);
48- Handler handler = ((Gallery) mContext).getHandler();
49- ContentObserver albumObserver = new ContentObserver(handler) {
65+ final Handler handler = ((Gallery) mContext).getHandler();
66+ final ContentObserver albumObserver = new ContentObserver(handler) {
5067 public void onChange(boolean selfChange) {
5168 loadMediaSetsIntoFeed(feed, true);
5269 }
@@ -77,34 +94,23 @@ public final class PicasaDataSource implements DataSource {
7794 }
7895
7996 protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) {
80- Account[] accounts = PicasaApi.getAccounts(mContext);
81- int numAccounts = accounts.length;
82- for (int i = 0; i < numAccounts; ++i) {
83- Account account = accounts[i];
84- boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
85- String username = account.name;
86- if (username.contains("@gmail.") || username.contains("@googlemail.")) {
87- // Strip the domain from GMail accounts for canonicalization. TODO: is there an official way?
88- username = username.substring(0, username.indexOf('@'));
89- }
90- mAccountEnabled.put(username, new Boolean(isEnabled));
91- }
92- ContentProviderClient client = mProviderClient;
97+ final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext);
98+ final ContentProviderClient client = mProviderClient;
9399 if (client == null)
94100 return;
95101 try {
96- EntrySchema albumSchema = AlbumEntry.SCHEMA;
97- Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
102+ final EntrySchema albumSchema = AlbumEntry.SCHEMA;
103+ final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
98104 DEFAULT_BUCKET_SORT_ORDER);
99- AlbumEntry album = new AlbumEntry();
105+ final AlbumEntry album = new AlbumEntry();
100106 MediaSet mediaSet;
101107 if (cursor.moveToFirst()) {
102- int numAlbums = cursor.getCount();
103- ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
108+ final int numAlbums = cursor.getCount();
109+ final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
104110 do {
105111 albumSchema.cursorToObject(cursor, album);
106- Boolean accountEnabledObj = mAccountEnabled.get(album.user);
107- boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
112+ final Boolean accountEnabledObj = accountsEnabled.get(album.user);
113+ final boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
108114 if (accountEnabled) {
109115 mediaSet = feed.getMediaSet(album.id);
110116 if (mediaSet == null) {
@@ -128,14 +134,14 @@ public final class PicasaDataSource implements DataSource {
128134 }
129135
130136 private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) {
131- ContentProviderClient client = mProviderClient;
137+ final ContentProviderClient client = mProviderClient;
132138 Cursor cursor = null;
133139 try {
134140 // Query photos in the album.
135- EntrySchema photosSchema = PhotoProjection.SCHEMA;
136- String whereInAlbum = "album_id = " + Long.toString(set.mId);
141+ final EntrySchema photosSchema = PhotoProjection.SCHEMA;
142+ final String whereInAlbum = "album_id = " + Long.toString(set.mId);
137143 cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null);
138- PhotoProjection photo = new PhotoProjection();
144+ final PhotoProjection photo = new PhotoProjection();
139145 int count = cursor.getCount();
140146 if (count < end) {
141147 end = count;
@@ -143,7 +149,7 @@ public final class PicasaDataSource implements DataSource {
143149 set.setNumExpectedItems(count);
144150 set.generateTitle(true);
145151 // Move to the next unread item.
146- int newIndex = start + 1;
152+ final int newIndex = start + 1;
147153 if (newIndex > count || !cursor.move(newIndex)) {
148154 end = 0;
149155 cursor.close();
@@ -161,7 +167,7 @@ public final class PicasaDataSource implements DataSource {
161167 }
162168 for (int i = 0; i < end; ++i) {
163169 photosSchema.cursorToObject(cursor, photo);
164- MediaItem item = new MediaItem();
170+ final MediaItem item = new MediaItem();
165171 item.mId = photo.id;
166172 item.mEditUri = photo.editUri;
167173 item.mMimeType = photo.contentType;
@@ -189,9 +195,6 @@ public final class PicasaDataSource implements DataSource {
189195 }
190196 }
191197
192- public void prime(final MediaItem item) {
193- }
194-
195198 public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
196199 try {
197200 if (operation == MediaFeed.OPERATION_DELETE) {
--- a/src/com/cooliris/media/TimeBar.java
+++ b/src/com/cooliris/media/TimeBar.java
@@ -18,6 +18,7 @@ import android.graphics.PorterDuffXfermode;
1818 import android.graphics.Rect;
1919 import android.util.SparseArray;
2020 import android.view.MotionEvent;
21+
2122 import com.cooliris.media.RenderView.Lists;
2223
2324 public final class TimeBar extends Layer implements MediaFeed.Listener {