OSDN Git Service

Picking up dirty albums when something is deleted
[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                 mMediaFeedNeedsToRun = false;
435                 ArrayList<MediaSet> mediaSets = mMediaSets;
436                 synchronized (mediaSets) {
437                     int expandedSetIndex = mExpandedMediaSetIndex;
438                     if (expandedSetIndex >= mMediaSets.size()) {
439                         expandedSetIndex = Shared.INVALID;
440                     }
441                     if (expandedSetIndex == Shared.INVALID) {
442                         // We purge the sets outside this visibleRange.
443                         int numSets = mediaSets.size();
444                         IndexRange visibleRange = mVisibleRange;
445                         IndexRange bufferedRange = mBufferedRange;
446                         boolean scanMediaSets = true;
447                         for (int i = 0; i < numSets; ++i) {
448                             if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) {
449                                 MediaSet set = mediaSets.get(i);
450                                 int numItemsLoaded = set.mNumItemsLoaded;
451                                 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
452                                     dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
453                                     if (set.getNumExpectedItems() == 0) {
454                                         mediaSets.remove(set);
455                                         break;
456                                     }
457                                     if (mListener != null) {
458                                         mListener.onFeedChanged(this, false);
459                                     }
460                                     sleepMs = 100;
461                                     scanMediaSets = false;
462                                 }
463                                 if (!set.setContainsValidItems()) {
464                                     mediaSets.remove(set);
465                                     if (mListener != null) {
466                                         mListener.onFeedChanged(this, false);
467                                     }
468                                     break;
469                                 }
470                             }
471                         }
472                         numSets = mediaSets.size();
473                         for (int i = 0; i < numSets; ++i) {
474                             MediaSet set = mediaSets.get(i);
475                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
476                                 if (scanMediaSets) {
477                                     int numItemsLoaded = set.mNumItemsLoaded;
478                                     if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
479                                         dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
480                                         if (set.getNumExpectedItems() == 0) {
481                                             mediaSets.remove(set);
482                                             break;
483                                         }
484                                         if (mListener != null) {
485                                             mListener.onFeedChanged(this, false);
486                                         }
487                                         sleepMs = 100;
488                                         scanMediaSets = false;
489                                     }
490                                 }
491                             } else if (i < bufferedRange.begin || i > bufferedRange.end) {
492                                 // Purge this set to its initial status.
493                                 MediaClustering clustering = mClusterSets.get(set);
494                                 if (clustering != null) {
495                                     clustering.clear();
496                                     mClusterSets.remove(set);
497                                 }
498                                 if (set.getNumItems() != 0)
499                                     set.clear();
500                             }
501                         }
502                     }
503                     if (expandedSetIndex != Shared.INVALID) {
504                         int numSets = mMediaSets.size();
505                         for (int i = 0; i < numSets; ++i) {
506                             // Purge other sets.
507                             if (i != expandedSetIndex) {
508                                 MediaSet set = mediaSets.get(i);
509                                 MediaClustering clustering = mClusterSets.get(set);
510                                 if (clustering != null) {
511                                     clustering.clear();
512                                     mClusterSets.remove(set);
513                                 }
514                                 if (set.getNumItems() != 0)
515                                     set.clear();
516                             }
517                         }
518                         // Make sure all the items are loaded for the album.
519                         int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded;
520                         int requestedItems = mVisibleRange.end;
521                         // requestedItems count changes in clustering mode.
522                         if (mInClusteringMode && mClusterSets != null) {
523                             requestedItems = 0;
524                             MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex));
525                             if (clustering != null) {
526                                 ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
527                                 int numClusters = clusters.size();
528                                 for (int i = 0; i < numClusters; i++) {
529                                     requestedItems += clusters.get(i).getNumExpectedItems();
530                                 }
531                             }
532                         }
533                         MediaSet set = mediaSets.get(expandedSetIndex);
534                         if (numItemsLoaded < set.getNumExpectedItems()) {
535                             // We perform calculations for a window that gets anchored to a multiple of NUM_ITEMS_LOOKAHEAD.
536                             // The start of the window is 0, x, 2x, 3x ... etc where x = NUM_ITEMS_LOOKAHEAD.
537                             dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
538                                     * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
539                             if (set.getNumExpectedItems() == 0) {
540                                 mediaSets.remove(set);
541                                 mListener.onFeedChanged(this, false);
542                             }
543                             if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
544                                 mListener.onFeedChanged(this, false);
545                             }
546                         }
547                     }
548                     MediaFilter filter = mMediaFilter;
549                     if (filter != null && mMediaFilteredSet == null) {
550                         if (expandedSetIndex != Shared.INVALID) {
551                             MediaSet set = mediaSets.get(expandedSetIndex);
552                             ArrayList<MediaItem> items = set.getItems();
553                             int numItems = set.getNumItems();
554                             MediaSet filteredSet = new MediaSet();
555                             filteredSet.setNumExpectedItems(numItems);
556                             mMediaFilteredSet = filteredSet;
557                             for (int i = 0; i < numItems; ++i) {
558                                 MediaItem item = items.get(i);
559                                 if (filter.pass(item)) {
560                                     filteredSet.addItem(item);
561                                 }
562                             }
563                             filteredSet.updateNumExpectedItems();
564                             filteredSet.generateTitle(true);
565                         }
566                         updateListener(true);
567                     }
568                 }
569             }
570         }
571     }
572
573     public void expandMediaSet(int mediaSetIndex) {
574         // We need to check if this slot can be focused or not.
575         if (mListener != null) {
576             mListener.onFeedAboutToChange(this);
577         }
578         if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) {
579             // We are collapsing a previously expanded media set
580             if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) {
581                 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex);
582                 if (set.getNumItems() == 0) {
583                     set.clear();
584                 }
585             }
586         }
587         mExpandedMediaSetIndex = mediaSetIndex;
588         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
589             // Notify Picasa that the user entered the album.
590             // MediaSet set = mMediaSets.get(mediaSetIndex);
591             // PicasaService.requestSync(mContext,
592             // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId);
593         }
594         updateListener(true);
595         mMediaFeedNeedsToRun = true;
596     }
597
598     public boolean canExpandSet(int slotIndex) {
599         int mediaSetIndex = slotIndex;
600         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
601             MediaSet set = mMediaSets.get(mediaSetIndex);
602             if (set.getNumItems() > 0) {
603                 MediaItem item = set.getItems().get(0);
604                 if (item.mId == Shared.INVALID) {
605                     return false;
606                 }
607                 return true;
608             }
609         }
610         return false;
611     }
612
613     public boolean hasExpandedMediaSet() {
614         return (mExpandedMediaSetIndex != Shared.INVALID);
615     }
616
617     public boolean restorePreviousClusteringState() {
618         boolean retVal = disableClusteringIfNecessary();
619         if (retVal) {
620             if (mListener != null) {
621                 mListener.onFeedAboutToChange(this);
622             }
623             updateListener(true);
624             mMediaFeedNeedsToRun = true;
625         }
626         return retVal;
627     }
628
629     private boolean disableClusteringIfNecessary() {
630         if (mInClusteringMode) {
631             // Disable clustering.
632             mInClusteringMode = false;
633             mMediaFeedNeedsToRun = true;
634             return true;
635         }
636         return false;
637     }
638
639     public boolean isClustered() {
640         return mInClusteringMode;
641     }
642
643     public MediaSet getCurrentSet() {
644         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
645             return mMediaSets.get(mExpandedMediaSetIndex);
646         }
647         return null;
648     }
649
650     public void performClustering() {
651         if (mListener != null) {
652             mListener.onFeedAboutToChange(this);
653         }
654         MediaSet setToUse = null;
655         if (mExpandedMediaSetIndex != Shared.INVALID || mExpandedMediaSetIndex < mMediaSets.size()) {
656             setToUse = mMediaSets.get(mExpandedMediaSetIndex);
657         }
658         if (setToUse != null) {
659             MediaClustering clustering = null;
660             synchronized (mClusterSets) {
661                 // Make sure the computation is completed to the end.
662                 clustering = mClusterSets.get(setToUse);
663                 if (clustering != null) {
664                     clustering.compute(null, true);
665                 } else {
666                     return;
667                 }
668             }
669             mInClusteringMode = true;
670             mMediaFeedNeedsToRun = true;
671             updateListener(true);
672         }
673     }
674
675     public void moveSetToFront(MediaSet mediaSet) {
676         ArrayList<MediaSet> mediaSets = mMediaSets;
677         int numSets = mediaSets.size();
678         if (numSets == 0) {
679             mediaSets.add(mediaSet);
680             return;
681         }
682         MediaSet setToFind = mediaSets.get(0);
683         if (setToFind == mediaSet) {
684             return;
685         }
686         mediaSets.set(0, mediaSet);
687         int indexToSwapTill = -1;
688         for (int i = 1; i < numSets; ++i) {
689             MediaSet set = mediaSets.get(i);
690             if (set == mediaSet) {
691                 mediaSets.set(i, setToFind);
692                 indexToSwapTill = i;
693                 break;
694             }
695         }
696         if (indexToSwapTill != Shared.INVALID) {
697             for (int i = indexToSwapTill; i > 1; --i) {
698                 MediaSet setEnd = mediaSets.get(i);
699                 MediaSet setPrev = mediaSets.get(i - 1);
700                 mediaSets.set(i, setPrev);
701                 mediaSets.set(i - 1, setEnd);
702             }
703         }
704         mMediaFeedNeedsToRun = true;
705     }
706
707     public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
708         MediaSet mediaSet = new MediaSet(dataSource);
709         mediaSet.mId = setId;
710         ArrayList<MediaSet> mediaSets = mMediaSets;
711         int numSets = mediaSets.size();
712         for (int i = 0; i < numSets; ++i) {
713             final MediaSet thisSet = mediaSets.get(i);
714             if (thisSet.mId == setId) {
715                 mediaSet.mName = thisSet.mName;
716                 mediaSet.mHasImages = thisSet.mHasImages;
717                 mediaSet.mHasVideos = thisSet.mHasVideos;
718                 mediaSets.set(i, mediaSet);
719                 break;
720             }
721         }
722         mMediaFeedNeedsToRun = true;
723         return mediaSet;
724     }
725
726     public void setSingleImageMode(boolean singleImageMode) {
727         mSingleImageMode = singleImageMode;
728     }
729
730     public boolean isSingleImageMode() {
731         return mSingleImageMode;
732     }
733
734     public MediaSet getExpandedMediaSet() {
735         if (mExpandedMediaSetIndex == Shared.INVALID)
736             return null;
737         if (mExpandedMediaSetIndex >= mMediaSets.size())
738             return null;
739         return mMediaSets.get(mExpandedMediaSetIndex);
740     }
741 }