packages/apps/Gallery2
修订版 | dc901a257e4487f3e45ed49316dffb1784f29815 (tree) |
---|---|
时间 | 2009-12-04 11:52:47 |
作者 | Dave Sparks <davidsparks@andr...> |
Commiter | Dave Sparks |
Updates to 3D gallery. Build 1203.
@@ -1,19 +1,25 @@ | ||
1 | 1 | package com.cooliris.cache; |
2 | 2 | |
3 | -import com.cooliris.media.Gallery; | |
3 | +import com.cooliris.media.LocalDataSource; | |
4 | 4 | import com.cooliris.media.SingleDataSource; |
5 | 5 | |
6 | 6 | import android.content.BroadcastReceiver; |
7 | +import android.content.ContentResolver; | |
7 | 8 | import android.content.Context; |
8 | 9 | import android.content.Intent; |
10 | +import android.database.ContentObserver; | |
9 | 11 | import android.net.Uri; |
12 | +import android.os.Handler; | |
13 | +import android.provider.MediaStore.Images; | |
14 | +import android.provider.MediaStore.Video; | |
10 | 15 | import android.util.Log; |
11 | 16 | |
12 | 17 | 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(); | |
14 | 20 | |
15 | 21 | @Override |
16 | - public void onReceive(Context context, Intent intent) { | |
22 | + public void onReceive(final Context context, Intent intent) { | |
17 | 23 | final String action = intent.getAction(); |
18 | 24 | Log.i(TAG, "Got intent with action " + action); |
19 | 25 | if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) { |
@@ -29,7 +35,21 @@ public class BootReceiver extends BroadcastReceiver { | ||
29 | 35 | CacheService.markDirty(context); |
30 | 36 | } |
31 | 37 | } 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); | |
33 | 53 | } |
34 | 54 | } |
35 | 55 | } |
@@ -24,6 +24,7 @@ import android.content.ContentValues; | ||
24 | 24 | import android.content.Context; |
25 | 25 | import android.content.Intent; |
26 | 26 | import android.database.Cursor; |
27 | +import android.database.MergeCursor; | |
27 | 28 | import android.graphics.Bitmap; |
28 | 29 | import android.graphics.Canvas; |
29 | 30 | import android.graphics.Paint; |
@@ -53,1026 +54,1153 @@ import com.cooliris.media.UriTexture; | ||
53 | 54 | import com.cooliris.media.Utils; |
54 | 55 | |
55 | 56 | 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 | + } | |
1078 | 1206 | } |
@@ -21,7 +21,7 @@ public class BackgroundLayer extends Layer { | ||
21 | 21 | private static final int ADAPTIVE_BACKGROUND_WIDTH = 256; |
22 | 22 | private static final int ADAPTIVE_BACKGROUND_HEIGHT = 128; |
23 | 23 | |
24 | - BackgroundLayer(GridLayer layer) { | |
24 | + public BackgroundLayer(GridLayer layer) { | |
25 | 25 | mGridLayer = layer; |
26 | 26 | } |
27 | 27 |
@@ -2,6 +2,7 @@ package com.cooliris.media; | ||
2 | 2 | |
3 | 3 | import java.util.ArrayList; |
4 | 4 | |
5 | + | |
5 | 6 | import android.util.Log; |
6 | 7 | |
7 | 8 | public final class ConcatenatedDataSource implements DataSource { |
@@ -2,6 +2,7 @@ package com.cooliris.media; | ||
2 | 2 | |
3 | 3 | import java.util.ArrayList; |
4 | 4 | |
5 | + | |
5 | 6 | public interface DataSource { |
6 | 7 | // Load the sets to be displayed. |
7 | 8 | void loadMediaSets(final MediaFeed feed); |
@@ -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 | -//} |
@@ -2,6 +2,7 @@ package com.cooliris.media; | ||
2 | 2 | |
3 | 3 | import java.io.IOException; |
4 | 4 | import java.net.URISyntaxException; |
5 | +import java.util.HashMap; | |
5 | 6 | import java.util.TimeZone; |
6 | 7 | |
7 | 8 | import android.app.Activity; |
@@ -27,349 +28,365 @@ import com.cooliris.wallpaper.RandomDataSource; | ||
27 | 28 | import com.cooliris.wallpaper.Slideshow; |
28 | 29 | |
29 | 30 | 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; | |
33 | 34 | |
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; | |
45 | 46 | |
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 | + } | |
140 | 138 | |
141 | - public ReverseGeocoder getReverseGeocoder() { | |
142 | - return mReverseGeocoder; | |
143 | - } | |
139 | + public ReverseGeocoder getReverseGeocoder() { | |
140 | + return mReverseGeocoder; | |
141 | + } | |
144 | 142 | |
145 | - public Handler getHandler() { | |
146 | - return mHandler; | |
147 | - } | |
143 | + public Handler getHandler() { | |
144 | + return mHandler; | |
145 | + } | |
148 | 146 | |
149 | - @Override | |
150 | - public void onRestart() { | |
151 | - super.onRestart(); | |
152 | - } | |
147 | + @Override | |
148 | + public void onRestart() { | |
149 | + super.onRestart(); | |
150 | + } | |
153 | 151 | |
154 | - @Override | |
155 | - public void onStart() { | |
156 | - super.onStart(); | |
157 | - } | |
152 | + @Override | |
153 | + public void onStart() { | |
154 | + super.onStart(); | |
155 | + } | |
158 | 156 | |
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 | + } | |
172 | 187 | |
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 | + } | |
180 | 195 | |
181 | - public boolean isPaused() { | |
182 | - return mPause; | |
183 | - } | |
196 | + public boolean isPaused() { | |
197 | + return mPause; | |
198 | + } | |
184 | 199 | |
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 | + } | |
198 | 213 | |
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 | + } | |
226 | 241 | |
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 | + } | |
237 | 252 | |
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 | + } | |
246 | 261 | |
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 | + } | |
251 | 266 | |
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 | + } | |
256 | 271 | |
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 | + } | |
260 | 275 | |
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 | + } | |
264 | 279 | |
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 | + } | |
287 | 303 | |
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 | + } | |
294 | 310 | |
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 | + } | |
333 | 350 | |
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 | + } | |
337 | 354 | |
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 | + } | |
355 | 372 | |
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 | + } | |
375 | 392 | } |
@@ -1,5 +1,6 @@ | ||
1 | 1 | package com.cooliris.media; |
2 | 2 | |
3 | + | |
3 | 4 | public final class GridCameraManager { |
4 | 5 | private final GridCamera mCamera; |
5 | 6 | private static final Pool<Vector3f> sPool; |
@@ -182,6 +182,9 @@ public final class GridInputProcessor implements GestureDetector.OnGestureListen | ||
182 | 182 | layer.setZoomValue(1.0f); |
183 | 183 | } |
184 | 184 | if (keyCode == KeyEvent.KEYCODE_MENU) { |
185 | + if (mLayer.getFeed() != null && mLayer.getFeed().isSingleImageMode()) { | |
186 | + return true; | |
187 | + } | |
185 | 188 | if (layer.getHud().getMode() == HudLayer.MODE_NORMAL) |
186 | 189 | layer.enterSelectionMode(); |
187 | 190 | else |
@@ -7,1426 +7,1437 @@ import android.hardware.SensorEvent; | ||
7 | 7 | import android.opengl.GLU; |
8 | 8 | import android.os.PowerManager; |
9 | 9 | import android.os.PowerManager.WakeLock; |
10 | +import android.util.Log; | |
10 | 11 | import android.view.KeyEvent; |
11 | 12 | import android.view.MotionEvent; |
12 | 13 | import android.content.Context; |
13 | 14 | |
14 | 15 | 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 | + } | |
1431 | 1442 | |
1432 | 1443 | } |
@@ -396,6 +396,7 @@ public final class HudLayer extends Layer { | ||
396 | 396 | |
397 | 397 | void setGridLayer(GridLayer layer) { |
398 | 398 | mGridLayer = layer; |
399 | + updateViews(); | |
399 | 400 | } |
400 | 401 | |
401 | 402 | int getMode() { |
@@ -754,7 +755,6 @@ public final class HudLayer extends Layer { | ||
754 | 755 | public void enterSelectionMode() { |
755 | 756 | setAlpha(1.0f); |
756 | 757 | setMode(HudLayer.MODE_SELECT); |
757 | - | |
758 | 758 | // if we are in single view mode, show the bottom menu without the delete button. |
759 | 759 | if (mGridLayer.noDeleteMode()) { |
760 | 760 | mSelectionMenuBottom.setMenus(mSingleViewIntentBottomMenu); |
@@ -762,6 +762,22 @@ public final class HudLayer extends Layer { | ||
762 | 762 | mSelectionMenuBottom.setMenus(mNormalBottomMenu); |
763 | 763 | } |
764 | 764 | } |
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 | + } | |
765 | 781 | |
766 | 782 | public Layer getMenuBar() { |
767 | 783 | return mFullscreenMenu; |
@@ -32,6 +32,8 @@ public final class LocalDataSource implements DataSource { | ||
32 | 32 | public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING; |
33 | 33 | public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME); |
34 | 34 | public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME); |
35 | + | |
36 | + public static boolean sObserverActive = false; | |
35 | 37 | private boolean mDisableImages; |
36 | 38 | private boolean mDisableVideos; |
37 | 39 |
@@ -43,8 +45,7 @@ public final class LocalDataSource implements DataSource { | ||
43 | 45 | } |
44 | 46 | |
45 | 47 | private Context mContext; |
46 | - private ContentObserver mImagesObserver; | |
47 | - private ContentObserver mVideosObserver; | |
48 | + private ContentObserver mObserver; | |
48 | 49 | |
49 | 50 | public LocalDataSource(Context context) { |
50 | 51 | mContext = context; |
@@ -62,35 +63,29 @@ public final class LocalDataSource implements DataSource { | ||
62 | 63 | stopListeners(); |
63 | 64 | CacheService.loadMediaSets(feed, this, !mDisableImages, !mDisableVideos); |
64 | 65 | 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) { | |
79 | 67 | 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 | + }); | |
83 | 78 | } |
84 | 79 | }; |
85 | - | |
86 | - // Start listening. TODO: coalesce update notifications while mediascanner is active. | |
80 | + | |
81 | + // Start listening. | |
87 | 82 | Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI; |
88 | 83 | Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI; |
89 | 84 | 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; | |
94 | 89 | } |
95 | 90 | |
96 | 91 | public void shutdown() { |
@@ -101,23 +96,18 @@ public final class LocalDataSource implements DataSource { | ||
101 | 96 | |
102 | 97 | private void stopListeners() { |
103 | 98 | 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); | |
109 | 101 | } |
102 | + sObserverActive = false; | |
110 | 103 | } |
111 | - | |
112 | - protected void refresh(MediaFeed feed, long setIdToUse) { | |
104 | + | |
105 | + protected void refreshUI(MediaFeed feed, long setIdToUse) { | |
113 | 106 | if (setIdToUse == Shared.INVALID) { |
114 | 107 | return; |
115 | 108 | } |
116 | 109 | Log.i(TAG, "Refreshing local data source"); |
117 | - Gallery.NEEDS_REFRESH = true; | |
118 | 110 | if (feed.getMediaSet(setIdToUse) == null) { |
119 | - if (!CacheService.setHasItems(mContext.getContentResolver(), setIdToUse)) | |
120 | - return; | |
121 | 111 | MediaSet mediaSet = feed.addMediaSet(setIdToUse, this); |
122 | 112 | if (setIdToUse == CAMERA_BUCKET_ID) { |
123 | 113 | mediaSet.mName = CAMERA_STRING; |
@@ -125,8 +115,6 @@ public final class LocalDataSource implements DataSource { | ||
125 | 115 | mediaSet.mName = DOWNLOAD_STRING; |
126 | 116 | } |
127 | 117 | mediaSet.generateTitle(true); |
128 | - if (!CacheService.isPresentInCache(setIdToUse)) | |
129 | - CacheService.markDirty(mContext); | |
130 | 118 | } else { |
131 | 119 | MediaSet mediaSet = feed.replaceMediaSet(setIdToUse, this); |
132 | 120 | if (setIdToUse == CAMERA_BUCKET_ID) { |
@@ -135,7 +123,6 @@ public final class LocalDataSource implements DataSource { | ||
135 | 123 | mediaSet.mName = DOWNLOAD_STRING; |
136 | 124 | } |
137 | 125 | mediaSet.generateTitle(true); |
138 | - CacheService.markDirty(mContext, setIdToUse); | |
139 | 126 | } |
140 | 127 | } |
141 | 128 |
@@ -152,7 +139,7 @@ public final class LocalDataSource implements DataSource { | ||
152 | 139 | return; |
153 | 140 | } |
154 | 141 | 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) { | |
156 | 143 | mediaFeed.moveSetToFront(set); |
157 | 144 | } |
158 | 145 | } |
@@ -3,6 +3,7 @@ package com.cooliris.media; | ||
3 | 3 | import java.util.ArrayList; |
4 | 4 | import java.util.HashMap; |
5 | 5 | |
6 | + | |
6 | 7 | public final class MediaBucketList { |
7 | 8 | private static final Boolean TRUE = new Boolean(true); |
8 | 9 | private static final Boolean FALSE = new Boolean(false); |
@@ -30,14 +30,31 @@ public final class PicasaDataSource implements DataSource { | ||
30 | 30 | private ContentProviderClient mProviderClient; |
31 | 31 | private final Context mContext; |
32 | 32 | private ContentObserver mAlbumObserver; |
33 | - private HashMap<String, Boolean> mAccountEnabled = new HashMap<String, Boolean>(); | |
34 | 33 | |
35 | - public PicasaDataSource(Context context) { | |
34 | + public PicasaDataSource(final Context context) { | |
36 | 35 | mContext = context; |
37 | 36 | } |
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 | + } | |
38 | 54 | |
39 | 55 | 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) { | |
41 | 58 | mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY); |
42 | 59 | } |
43 | 60 | // 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 { | ||
45 | 62 | |
46 | 63 | // Ensure that users are up to date. TODO: also listen for accounts changed broadcast. |
47 | 64 | 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) { | |
50 | 67 | public void onChange(boolean selfChange) { |
51 | 68 | loadMediaSetsIntoFeed(feed, true); |
52 | 69 | } |
@@ -77,34 +94,23 @@ public final class PicasaDataSource implements DataSource { | ||
77 | 94 | } |
78 | 95 | |
79 | 96 | 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; | |
93 | 99 | if (client == null) |
94 | 100 | return; |
95 | 101 | 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, | |
98 | 104 | DEFAULT_BUCKET_SORT_ORDER); |
99 | - AlbumEntry album = new AlbumEntry(); | |
105 | + final AlbumEntry album = new AlbumEntry(); | |
100 | 106 | MediaSet mediaSet; |
101 | 107 | 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); | |
104 | 110 | do { |
105 | 111 | 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(); | |
108 | 114 | if (accountEnabled) { |
109 | 115 | mediaSet = feed.getMediaSet(album.id); |
110 | 116 | if (mediaSet == null) { |
@@ -128,14 +134,14 @@ public final class PicasaDataSource implements DataSource { | ||
128 | 134 | } |
129 | 135 | |
130 | 136 | private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) { |
131 | - ContentProviderClient client = mProviderClient; | |
137 | + final ContentProviderClient client = mProviderClient; | |
132 | 138 | Cursor cursor = null; |
133 | 139 | try { |
134 | 140 | // 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); | |
137 | 143 | cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null); |
138 | - PhotoProjection photo = new PhotoProjection(); | |
144 | + final PhotoProjection photo = new PhotoProjection(); | |
139 | 145 | int count = cursor.getCount(); |
140 | 146 | if (count < end) { |
141 | 147 | end = count; |
@@ -143,7 +149,7 @@ public final class PicasaDataSource implements DataSource { | ||
143 | 149 | set.setNumExpectedItems(count); |
144 | 150 | set.generateTitle(true); |
145 | 151 | // Move to the next unread item. |
146 | - int newIndex = start + 1; | |
152 | + final int newIndex = start + 1; | |
147 | 153 | if (newIndex > count || !cursor.move(newIndex)) { |
148 | 154 | end = 0; |
149 | 155 | cursor.close(); |
@@ -161,7 +167,7 @@ public final class PicasaDataSource implements DataSource { | ||
161 | 167 | } |
162 | 168 | for (int i = 0; i < end; ++i) { |
163 | 169 | photosSchema.cursorToObject(cursor, photo); |
164 | - MediaItem item = new MediaItem(); | |
170 | + final MediaItem item = new MediaItem(); | |
165 | 171 | item.mId = photo.id; |
166 | 172 | item.mEditUri = photo.editUri; |
167 | 173 | item.mMimeType = photo.contentType; |
@@ -189,9 +195,6 @@ public final class PicasaDataSource implements DataSource { | ||
189 | 195 | } |
190 | 196 | } |
191 | 197 | |
192 | - public void prime(final MediaItem item) { | |
193 | - } | |
194 | - | |
195 | 198 | public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) { |
196 | 199 | try { |
197 | 200 | if (operation == MediaFeed.OPERATION_DELETE) { |
@@ -18,6 +18,7 @@ import android.graphics.PorterDuffXfermode; | ||
18 | 18 | import android.graphics.Rect; |
19 | 19 | import android.util.SparseArray; |
20 | 20 | import android.view.MotionEvent; |
21 | + | |
21 | 22 | import com.cooliris.media.RenderView.Lists; |
22 | 23 | |
23 | 24 | public final class TimeBar extends Layer implements MediaFeed.Listener { |