OSDN Git Service

android-2.1_r1 snapshot
[android-x86/packages-apps-Gallery2.git] / src / com / cooliris / media / MediaFeed.java
1 package com.cooliris.media;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5
6 import android.content.Context;
7 import android.view.Gravity;
8 import android.widget.Toast;
9 import android.os.Process;
10
11 import com.cooliris.media.MediaClustering.Cluster;
12
13 public final class MediaFeed implements Runnable {
14     public static final int OPERATION_DELETE = 0;
15     public static final int OPERATION_ROTATE = 1;
16     public static final int OPERATION_CROP = 2;
17
18     private static final int NUM_ITEMS_LOOKAHEAD = 60;
19
20     private IndexRange mVisibleRange = new IndexRange();
21     private IndexRange mBufferedRange = new IndexRange();
22     private ArrayList<MediaSet> mMediaSets = new ArrayList<MediaSet>();
23     private Listener mListener;
24     private DataSource mDataSource;
25     private boolean mListenerNeedsUpdate = false;
26     private boolean mMediaFeedNeedsToRun = false;
27     private MediaSet mSingleWrapper = new MediaSet();
28     private boolean mInClusteringMode = false;
29     private HashMap<MediaSet, MediaClustering> mClusterSets = new HashMap<MediaSet, MediaClustering>(32);
30     private int mExpandedMediaSetIndex = Shared.INVALID;
31     private MediaFilter mMediaFilter;
32     private MediaSet mMediaFilteredSet;
33     private Context mContext;
34     private Thread mDataSourceThread = null;
35     private Thread mAlbumSourceThread = null;
36     private boolean mListenerNeedsLayout;
37     private boolean mWaitingForMediaScanner;
38     private boolean mSingleImageMode;
39     private boolean mLoading;
40
41     public interface Listener {
42         public abstract void onFeedAboutToChange(MediaFeed feed);
43
44         public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout);
45     }
46
47     public MediaFeed(Context context, DataSource dataSource, Listener listener) {
48         mContext = context;
49         mListener = listener;
50         mDataSource = dataSource;
51         mSingleWrapper.setNumExpectedItems(1);
52         mLoading = true;
53     }
54
55     public void shutdown() {
56         if (mDataSourceThread != null) {
57             mDataSource.shutdown();
58             mDataSourceThread.interrupt();
59             mDataSourceThread = null;
60         }
61         if (mAlbumSourceThread != null) {
62             mAlbumSourceThread.interrupt();
63             mAlbumSourceThread = null;
64         }
65         int numSets = mMediaSets.size();
66         for (int i = 0; i < numSets; ++i) {
67             MediaSet set = mMediaSets.get(i);
68             set.clear();
69         }
70         synchronized (mMediaSets) {
71             mMediaSets.clear();
72         }
73         int numClusters = mClusterSets.size();
74         for (int i = 0; i < numClusters; ++i) {
75             MediaClustering mc = mClusterSets.get(i);
76             if (mc != null) {
77                 mc.clear();
78             }
79         }
80         mClusterSets.clear();
81         mListener = null;
82         mDataSource = null;
83         mSingleWrapper = null;
84     }
85
86     public void setVisibleRange(int begin, int end) {
87         if (begin != mVisibleRange.begin || end != mVisibleRange.end) {
88             mVisibleRange.begin = begin;
89             mVisibleRange.end = end;
90             int numItems = 96;
91             int numItemsBy2 = numItems / 2;
92             int numItemsBy4 = numItems / 4;
93             mBufferedRange.begin = (begin / numItemsBy2) * numItemsBy2 - numItemsBy4;
94             mBufferedRange.end = mBufferedRange.begin + numItems;
95             mMediaFeedNeedsToRun = true;
96         }
97     }
98
99     public void setFilter(MediaFilter filter) {
100         mMediaFilter = filter;
101         mMediaFilteredSet = null;
102         if (mListener != null) {
103             mListener.onFeedAboutToChange(this);
104         }
105         mMediaFeedNeedsToRun = true;
106     }
107
108     public void removeFilter() {
109         mMediaFilter = null;
110         mMediaFilteredSet = null;
111         if (mListener != null) {
112             mListener.onFeedAboutToChange(this);
113             updateListener(true);
114         }
115         mMediaFeedNeedsToRun = true;
116     }
117
118     public ArrayList<MediaSet> getMediaSets() {
119         return mMediaSets;
120     }
121
122     public MediaSet getMediaSet(final long setId) {
123         if (setId != Shared.INVALID) {
124             try {
125                 int mMediaSetsSize = mMediaSets.size();
126                 for (int i = 0; i < mMediaSetsSize; i++) {
127                     if (mMediaSets.get(i).mId == setId) {
128                         return mMediaSets.get(i);
129                     }
130                 }
131             } catch (Exception e) {
132                 return null;
133             }
134         }
135         return null;
136     }
137
138     public MediaSet getFilteredSet() {
139         return mMediaFilteredSet;
140     }
141
142     public MediaSet addMediaSet(final long setId, DataSource dataSource) {
143         MediaSet mediaSet = new MediaSet(dataSource);
144         mediaSet.mId = setId;
145         mMediaSets.add(mediaSet);
146         if (mDataSourceThread != null && !mDataSourceThread.isAlive()) {
147             mDataSourceThread.start();
148         }
149         mMediaFeedNeedsToRun = true;
150         return mediaSet;
151     }
152
153     public DataSource getDataSource() {
154         return mDataSource;
155     }
156
157     public MediaClustering getClustering() {
158         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
159             return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex));
160         }
161         return null;
162     }
163
164     public ArrayList<Cluster> getClustersForSet(final MediaSet set) {
165         ArrayList<Cluster> clusters = null;
166         if (mClusterSets != null && mClusterSets.containsKey(set)) {
167             MediaClustering mediaClustering = mClusterSets.get(set);
168             if (mediaClustering != null) {
169                 clusters = mediaClustering.getClusters();
170             }
171         }
172         return clusters;
173     }
174
175     public void addItemToMediaSet(MediaItem item, MediaSet mediaSet) {
176         item.mParentMediaSet = mediaSet;
177         mediaSet.addItem(item);
178         synchronized (mClusterSets) {
179             if (item.mClusteringState == MediaItem.NOT_CLUSTERED) {
180                 MediaClustering clustering = mClusterSets.get(mediaSet);
181                 if (clustering == null) {
182                     clustering = new MediaClustering(mediaSet.isPicassaAlbum());
183                     mClusterSets.put(mediaSet, clustering);
184                 }
185                 clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems());
186                 clustering.addItemForClustering(item);
187                 item.mClusteringState = MediaItem.CLUSTERED;
188             }
189         }
190         mMediaFeedNeedsToRun = true;
191     }
192
193     public void performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
194         int numBuckets = mediaBuckets.size();
195         final ArrayList<MediaBucket> copyMediaBuckets = new ArrayList<MediaBucket>(numBuckets);
196         for (int i = 0; i < numBuckets; ++i) {
197             copyMediaBuckets.add(mediaBuckets.get(i));
198         }
199         if (operation == OPERATION_DELETE && mListener != null) {
200             mListener.onFeedAboutToChange(this);
201         }
202         Thread operationThread = new Thread(new Runnable() {
203             public void run() {
204                 ArrayList<MediaBucket> mediaBuckets = copyMediaBuckets;
205                 if (operation == OPERATION_DELETE) {
206                     int numBuckets = mediaBuckets.size();
207                     for (int i = 0; i < numBuckets; ++i) {
208                         MediaBucket bucket = mediaBuckets.get(i);
209                         MediaSet set = bucket.mediaSet;
210                         ArrayList<MediaItem> items = bucket.mediaItems;
211                         if (set != null && items == null) {
212                             // Remove the entire bucket.
213                             removeMediaSet(set);
214                         } else if (set != null && items != null) {
215                             // We need to remove these items from the set.
216                             int numItems = items.size();
217                             // We also need to delete the items from the
218                             // cluster.
219                             MediaClustering clustering = mClusterSets.get(set);
220                             for (int j = 0; j < numItems; ++j) {
221                                 MediaItem item = items.get(j);
222                                 removeItemFromMediaSet(item, set);
223                                 if (clustering != null) {
224                                     clustering.removeItemFromClustering(item);
225                                 }
226                             }
227                             set.updateNumExpectedItems();
228                             set.generateTitle(true);
229                         }
230                     }
231                     updateListener(true);
232                     mMediaFeedNeedsToRun = true;
233                     if (mDataSource != null) {
234                         mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null);
235                     }
236                 } else {
237                     mDataSource.performOperation(operation, mediaBuckets, data);
238                 }
239             }
240         });
241         operationThread.setName("Operation " + operation);
242         operationThread.start();
243     }
244
245     public void removeMediaSet(MediaSet set) {
246         synchronized (mMediaSets) {
247             mMediaSets.remove(set);
248         }
249         mMediaFeedNeedsToRun = true;
250     }
251
252     private void removeItemFromMediaSet(MediaItem item, MediaSet mediaSet) {
253         mediaSet.removeItem(item);
254         synchronized (mClusterSets) {
255             MediaClustering clustering = mClusterSets.get(mediaSet);
256             if (clustering != null) {
257                 clustering.removeItemFromClustering(item);
258             }
259         }
260         mMediaFeedNeedsToRun = true;
261     }
262
263     public void updateListener(boolean needsLayout) {
264         mListenerNeedsUpdate = true;
265         mListenerNeedsLayout = needsLayout;
266     }
267
268     public int getNumSlots() {
269         int currentMediaSetIndex = mExpandedMediaSetIndex;
270         ArrayList<MediaSet> mediaSets = mMediaSets;
271         int mediaSetsSize = mediaSets.size();
272
273         if (mInClusteringMode == false) {
274             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
275                 return mediaSetsSize;
276             } else {
277                 MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
278                 return setToUse.getNumItems();
279             }
280         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
281             MediaSet set = mediaSets.get(currentMediaSetIndex);
282             MediaClustering clustering = mClusterSets.get(set);
283             if (clustering != null) {
284                 return clustering.getClustersForDisplay().size();
285             }
286         }
287         return 0;
288     }
289
290     public MediaSet getSetForSlot(int slotIndex) {
291         if (slotIndex < 0) {
292             return null;
293         }
294
295         ArrayList<MediaSet> mediaSets = mMediaSets;
296         int mediaSetsSize = mediaSets.size();
297         int currentMediaSetIndex = mExpandedMediaSetIndex;
298
299         if (mInClusteringMode == false) {
300             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
301                 if (slotIndex >= mediaSetsSize) {
302                     return null;
303                 }
304                 return mMediaSets.get(slotIndex);
305             }
306             if (mSingleWrapper.getNumItems() == 0) {
307                 mSingleWrapper.addItem(null);
308             }
309             MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
310             ArrayList<MediaItem> items = setToUse.getItems();
311             if (slotIndex >= setToUse.getNumItems()) {
312                 return null;
313             }
314             mSingleWrapper.getItems().set(0, items.get(slotIndex));
315             return mSingleWrapper;
316         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
317             MediaSet set = mediaSets.get(currentMediaSetIndex);
318             MediaClustering clustering = mClusterSets.get(set);
319             if (clustering != null) {
320                 ArrayList<MediaClustering.Cluster> clusters = clustering.getClustersForDisplay();
321                 if (clusters.size() > slotIndex) {
322                     MediaClustering.Cluster cluster = clusters.get(slotIndex);
323                     cluster.generateCaption(mContext);
324                     return cluster;
325                 }
326             }
327         }
328         return null;
329     }
330
331     public boolean getWaitingForMediaScanner() {
332         return mWaitingForMediaScanner;
333     }
334
335     public boolean isLoading() {
336         return mLoading;
337     }
338
339     public void start() {
340         final MediaFeed feed = this;
341         mLoading = true;
342         mDataSourceThread = new Thread(this);
343         mDataSourceThread.setName("MediaFeed");
344         mAlbumSourceThread = new Thread(new Runnable() {
345             public void run() {
346                 if (mContext == null)
347                     return;
348                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
349                 DataSource dataSource = mDataSource;
350                 // We must wait while the SD card is mounted or the MediaScanner
351                 // is running.
352                 if (dataSource != null) {
353                     dataSource.loadMediaSets(feed);
354                 }
355                 mWaitingForMediaScanner = false;
356                 while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) {
357                     // MediaScanner is still running, wait
358                     if (Thread.interrupted())
359                         return;
360                     mWaitingForMediaScanner = true;
361                     try {
362                         if (mContext == null)
363                             return;
364                         showToast(mContext.getResources().getString(R.string.initializing), Toast.LENGTH_LONG);
365                         Thread.sleep(6000);
366                     } catch (InterruptedException e) {
367                         return;
368                     }
369                 }
370                 if (mWaitingForMediaScanner) {
371                     showToast(mContext.getResources().getString(R.string.loading_new), Toast.LENGTH_LONG);
372                     mWaitingForMediaScanner = false;
373                     if (dataSource != null) {
374                         dataSource.loadMediaSets(feed);
375                     }
376                 }
377                 mLoading = false;
378             }
379         });
380         mAlbumSourceThread.setName("MediaSets");
381         mAlbumSourceThread.start();
382     }
383
384     private void showToast(final String string, final int duration) {
385         showToast(string, duration, false);
386     }
387
388     private void showToast(final String string, final int duration, final boolean centered) {
389         if (mContext != null && !((Gallery) mContext).isPaused()) {
390             ((Gallery) mContext).getHandler().post(new Runnable() {
391                 public void run() {
392                     if (mContext != null) {
393                         Toast toast = Toast.makeText(mContext, string, duration);
394                         if (centered) {
395                             toast.setGravity(Gravity.CENTER, 0, 0);
396                         }
397                         toast.show();
398                     }
399                 }
400             });
401         }
402     }
403
404     public void run() {
405         DataSource dataSource = mDataSource;
406         int sleepMs = 10;
407         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
408         if (dataSource != null) {
409             while (!Thread.interrupted()) {
410                 if (mListenerNeedsUpdate) {
411                     mListenerNeedsUpdate = false;
412                     if (mListener != null)
413                         mListener.onFeedChanged(this, mListenerNeedsLayout);
414                     try {
415                         Thread.sleep(sleepMs);
416                     } catch (InterruptedException e) {
417                         return;
418                     }
419                 } else {
420                     if (mWaitingForMediaScanner) {
421                         synchronized (mMediaSets) {
422                             mMediaSets.clear();
423                         }
424                     }
425                     try {
426                         Thread.sleep(sleepMs);
427                     } catch (InterruptedException e) {
428                         return;
429                     }
430                 }
431                 sleepMs = 300;
432                 if (!mMediaFeedNeedsToRun)
433                     continue;
434                 if (((Gallery) mContext).isPaused())
435                     continue;
436                 mMediaFeedNeedsToRun = false;
437                 ArrayList<MediaSet> mediaSets = mMediaSets;
438                 synchronized (mediaSets) {
439                     int expandedSetIndex = mExpandedMediaSetIndex;
440                     if (expandedSetIndex >= mMediaSets.size()) {
441                         expandedSetIndex = Shared.INVALID;
442                     }
443                     if (expandedSetIndex == Shared.INVALID) {
444                         // We purge the sets outside this visibleRange.
445                         int numSets = mediaSets.size();
446                         IndexRange visibleRange = mVisibleRange;
447                         IndexRange bufferedRange = mBufferedRange;
448                         boolean scanMediaSets = true;
449                         for (int i = 0; i < numSets; ++i) {
450                             if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) {
451                                 MediaSet set = mediaSets.get(i);
452                                 int numItemsLoaded = set.mNumItemsLoaded;
453                                 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
454                                     dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
455                                     if (set.getNumExpectedItems() == 0) {
456                                         mediaSets.remove(set);
457                                         break;
458                                     }
459                                     if (mListener != null) {
460                                         mListener.onFeedChanged(this, false);
461                                     }
462                                     sleepMs = 100;
463                                     scanMediaSets = false;
464                                 }
465                                 if (!set.setContainsValidItems()) {
466                                     mediaSets.remove(set);
467                                     if (mListener != null) {
468                                         mListener.onFeedChanged(this, false);
469                                     }
470                                     break;
471                                 }
472                             }
473                         }
474                         numSets = mediaSets.size();
475                         for (int i = 0; i < numSets; ++i) {
476                             MediaSet set = mediaSets.get(i);
477                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
478                                 if (scanMediaSets) {
479                                     int numItemsLoaded = set.mNumItemsLoaded;
480                                     if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
481                                         dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
482                                         if (set.getNumExpectedItems() == 0) {
483                                             mediaSets.remove(set);
484                                             break;
485                                         }
486                                         if (mListener != null) {
487                                             mListener.onFeedChanged(this, false);
488                                         }
489                                         sleepMs = 100;
490                                         scanMediaSets = false;
491                                     }
492                                 }
493                             } else if (i < bufferedRange.begin || i > bufferedRange.end) {
494                                 // Purge this set to its initial status.
495                                 MediaClustering clustering = mClusterSets.get(set);
496                                 if (clustering != null) {
497                                     clustering.clear();
498                                     mClusterSets.remove(set);
499                                 }
500                                 if (set.getNumItems() != 0)
501                                     set.clear();
502                             }
503                         }
504                     }
505                     if (expandedSetIndex != Shared.INVALID) {
506                         int numSets = mMediaSets.size();
507                         for (int i = 0; i < numSets; ++i) {
508                             // Purge other sets.
509                             if (i != expandedSetIndex) {
510                                 MediaSet set = mediaSets.get(i);
511                                 MediaClustering clustering = mClusterSets.get(set);
512                                 if (clustering != null) {
513                                     clustering.clear();
514                                     mClusterSets.remove(set);
515                                 }
516                                 if (set.getNumItems() != 0)
517                                     set.clear();
518                             }
519                         }
520                         // Make sure all the items are loaded for the album.
521                         int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded;
522                         int requestedItems = mVisibleRange.end;
523                         // requestedItems count changes in clustering mode.
524                         if (mInClusteringMode && mClusterSets != null) {
525                             requestedItems = 0;
526                             MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex));
527                             if (clustering != null) {
528                                 ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
529                                 int numClusters = clusters.size();
530                                 for (int i = 0; i < numClusters; i++) {
531                                     requestedItems += clusters.get(i).getNumExpectedItems();
532                                 }
533                             }
534                         }
535                         MediaSet set = mediaSets.get(expandedSetIndex);
536                         if (numItemsLoaded < set.getNumExpectedItems()) {
537                             // We perform calculations for a window that gets anchored to a multiple of NUM_ITEMS_LOOKAHEAD.
538                             // The start of the window is 0, x, 2x, 3x ... etc where x = NUM_ITEMS_LOOKAHEAD.
539                             dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
540                                     * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
541                             if (set.getNumExpectedItems() == 0) {
542                                 mediaSets.remove(set);
543                                 mListener.onFeedChanged(this, false);
544                             }
545                             if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
546                                 mListener.onFeedChanged(this, false);
547                             }
548                         }
549                     }
550                     MediaFilter filter = mMediaFilter;
551                     if (filter != null && mMediaFilteredSet == null) {
552                         if (expandedSetIndex != Shared.INVALID) {
553                             MediaSet set = mediaSets.get(expandedSetIndex);
554                             ArrayList<MediaItem> items = set.getItems();
555                             int numItems = set.getNumItems();
556                             MediaSet filteredSet = new MediaSet();
557                             filteredSet.setNumExpectedItems(numItems);
558                             mMediaFilteredSet = filteredSet;
559                             for (int i = 0; i < numItems; ++i) {
560                                 MediaItem item = items.get(i);
561                                 if (filter.pass(item)) {
562                                     filteredSet.addItem(item);
563                                 }
564                             }
565                             filteredSet.updateNumExpectedItems();
566                             filteredSet.generateTitle(true);
567                         }
568                         updateListener(true);
569                     }
570                 }
571             }
572         }
573     }
574
575     public void expandMediaSet(int mediaSetIndex) {
576         // We need to check if this slot can be focused or not.
577         if (mListener != null) {
578             mListener.onFeedAboutToChange(this);
579         }
580         if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) {
581             // We are collapsing a previously expanded media set
582             if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) {
583                 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex);
584                 if (set.getNumItems() == 0) {
585                     set.clear();
586                 }
587             }
588         }
589         mExpandedMediaSetIndex = mediaSetIndex;
590         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
591             // Notify Picasa that the user entered the album.
592             // MediaSet set = mMediaSets.get(mediaSetIndex);
593             // PicasaService.requestSync(mContext,
594             // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId);
595         }
596         updateListener(true);
597         mMediaFeedNeedsToRun = true;
598     }
599
600     public boolean canExpandSet(int slotIndex) {
601         int mediaSetIndex = slotIndex;
602         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
603             MediaSet set = mMediaSets.get(mediaSetIndex);
604             if (set.getNumItems() > 0) {
605                 MediaItem item = set.getItems().get(0);
606                 if (item.mId == Shared.INVALID) {
607                     return false;
608                 }
609                 return true;
610             }
611         }
612         return false;
613     }
614
615     public boolean hasExpandedMediaSet() {
616         return (mExpandedMediaSetIndex != Shared.INVALID);
617     }
618
619     public boolean restorePreviousClusteringState() {
620         boolean retVal = disableClusteringIfNecessary();
621         if (retVal) {
622             if (mListener != null) {
623                 mListener.onFeedAboutToChange(this);
624             }
625             updateListener(true);
626             mMediaFeedNeedsToRun = true;
627         }
628         return retVal;
629     }
630
631     private boolean disableClusteringIfNecessary() {
632         if (mInClusteringMode) {
633             // Disable clustering.
634             mInClusteringMode = false;
635             mMediaFeedNeedsToRun = true;
636             return true;
637         }
638         return false;
639     }
640
641     public boolean isClustered() {
642         return mInClusteringMode;
643     }
644
645     public MediaSet getCurrentSet() {
646         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
647             return mMediaSets.get(mExpandedMediaSetIndex);
648         }
649         return null;
650     }
651
652     public void performClustering() {
653         if (mListener != null) {
654             mListener.onFeedAboutToChange(this);
655         }
656         MediaSet setToUse = null;
657         if (mExpandedMediaSetIndex != Shared.INVALID || mExpandedMediaSetIndex < mMediaSets.size()) {
658             setToUse = mMediaSets.get(mExpandedMediaSetIndex);
659         }
660         if (setToUse != null) {
661             MediaClustering clustering = null;
662             synchronized (mClusterSets) {
663                 // Make sure the computation is completed to the end.
664                 clustering = mClusterSets.get(setToUse);
665                 if (clustering != null) {
666                     clustering.compute(null, true);
667                 } else {
668                     return;
669                 }
670             }
671             mInClusteringMode = true;
672             mMediaFeedNeedsToRun = true;
673             updateListener(true);
674         }
675     }
676
677     public void moveSetToFront(MediaSet mediaSet) {
678         ArrayList<MediaSet> mediaSets = mMediaSets;
679         int numSets = mediaSets.size();
680         if (numSets == 0) {
681             mediaSets.add(mediaSet);
682             return;
683         }
684         MediaSet setToFind = mediaSets.get(0);
685         if (setToFind == mediaSet) {
686             return;
687         }
688         mediaSets.set(0, mediaSet);
689         int indexToSwapTill = -1;
690         for (int i = 1; i < numSets; ++i) {
691             MediaSet set = mediaSets.get(i);
692             if (set == mediaSet) {
693                 mediaSets.set(i, setToFind);
694                 indexToSwapTill = i;
695                 break;
696             }
697         }
698         if (indexToSwapTill != Shared.INVALID) {
699             for (int i = indexToSwapTill; i > 1; --i) {
700                 MediaSet setEnd = mediaSets.get(i);
701                 MediaSet setPrev = mediaSets.get(i - 1);
702                 mediaSets.set(i, setPrev);
703                 mediaSets.set(i - 1, setEnd);
704             }
705         }
706         mMediaFeedNeedsToRun = true;
707     }
708
709     public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
710         MediaSet mediaSet = new MediaSet(dataSource);
711         mediaSet.mId = setId;
712         ArrayList<MediaSet> mediaSets = mMediaSets;
713         int numSets = mediaSets.size();
714         for (int i = 0; i < numSets; ++i) {
715             final MediaSet thisSet = mediaSets.get(i);
716             if (thisSet.mId == setId) {
717                 mediaSet.mName = thisSet.mName;
718                 mediaSet.mHasImages = thisSet.mHasImages;
719                 mediaSet.mHasVideos = thisSet.mHasVideos;
720                 mediaSets.set(i, mediaSet);
721                 break;
722             }
723         }
724         mMediaFeedNeedsToRun = true;
725         return mediaSet;
726     }
727
728     public void setSingleImageMode(boolean singleImageMode) {
729         mSingleImageMode = singleImageMode;
730     }
731
732     public boolean isSingleImageMode() {
733         return mSingleImageMode;
734     }
735
736     public MediaSet getExpandedMediaSet() {
737         if (mExpandedMediaSetIndex == Shared.INVALID)
738             return null;
739         if (mExpandedMediaSetIndex >= mMediaSets.size())
740             return null;
741         return mMediaSets.get(mExpandedMediaSetIndex);
742     }
743 }