OSDN Git Service

d2e4a280c3c1f34597d714f408985be2e1e65887
[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                     mWaitingForMediaScanner = true;
359                     try {
360                         if (mContext == null)
361                             return;
362                         showToast(mContext.getResources().getString(R.string.initializing), Toast.LENGTH_LONG);
363                         Thread.sleep(6000);
364                     } catch (InterruptedException e) {
365
366                     }
367                 }
368                 if (mWaitingForMediaScanner) {
369                     showToast(mContext.getResources().getString(R.string.loading_new), Toast.LENGTH_LONG);
370                     mWaitingForMediaScanner = false;
371                     if (dataSource != null) {
372                         dataSource.loadMediaSets(feed);
373                     }
374                 }
375                 mLoading = false;
376             }
377         });
378         mAlbumSourceThread.setName("MediaSets");
379         mAlbumSourceThread.start();
380     }
381
382     private void showToast(final String string, final int duration) {
383         showToast(string, duration, false);
384     }
385
386     private void showToast(final String string, final int duration, final boolean centered) {
387         if (mContext != null && !((Gallery) mContext).isPaused()) {
388             ((Gallery) mContext).getHandler().post(new Runnable() {
389                 public void run() {
390                     if (mContext != null) {
391                         Toast toast = Toast.makeText(mContext, string, duration);
392                         if (centered) {
393                             toast.setGravity(Gravity.CENTER, 0, 0);
394                         }
395                         toast.show();
396                     }
397                 }
398             });
399         }
400     }
401
402     public void run() {
403         DataSource dataSource = mDataSource;
404         int sleepMs = 10;
405         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
406         if (dataSource != null) {
407             while (!Thread.interrupted()) {
408                 if (mListenerNeedsUpdate) {
409                     mListenerNeedsUpdate = false;
410                     if (mListener != null)
411                         mListener.onFeedChanged(this, mListenerNeedsLayout);
412                     try {
413                         Thread.sleep(sleepMs);
414                     } catch (InterruptedException e) {
415                         return;
416                     }
417                 } else {
418                     if (mWaitingForMediaScanner) {
419                         synchronized (mMediaSets) {
420                             mMediaSets.clear();
421                         }
422                     }
423                     try {
424                         Thread.sleep(sleepMs);
425                     } catch (InterruptedException e) {
426                         return;
427                     }
428                 }
429                 sleepMs = 300;
430                 if (!mMediaFeedNeedsToRun)
431                     continue;
432                 mMediaFeedNeedsToRun = false;
433                 ArrayList<MediaSet> mediaSets = mMediaSets;
434                 synchronized (mediaSets) {
435                     int expandedSetIndex = mExpandedMediaSetIndex;
436                     if (expandedSetIndex >= mMediaSets.size()) {
437                         expandedSetIndex = Shared.INVALID;
438                     }
439                     if (expandedSetIndex == Shared.INVALID) {
440                         // We purge the sets outside this visibleRange.
441                         int numSets = mediaSets.size();
442                         IndexRange visibleRange = mVisibleRange;
443                         IndexRange bufferedRange = mBufferedRange;
444                         boolean scanMediaSets = true;
445                         for (int i = 0; i < numSets; ++i) {
446                             if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) {
447                                 MediaSet set = mediaSets.get(i);
448                                 int numItemsLoaded = set.mNumItemsLoaded;
449                                 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
450                                     dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
451                                     if (set.getNumExpectedItems() == 0) {
452                                         mediaSets.remove(set);
453                                         break;
454                                     }
455                                     if (mListener != null) {
456                                         mListener.onFeedChanged(this, false);
457                                     }
458                                     sleepMs = 100;
459                                     scanMediaSets = false;
460                                 }
461                                 if (!set.setContainsValidItems()) {
462                                     mediaSets.remove(set);
463                                     if (mListener != null) {
464                                         mListener.onFeedChanged(this, false);
465                                     }
466                                     break;
467                                 }
468                             }
469                         }
470                         numSets = mediaSets.size();
471                         for (int i = 0; i < numSets; ++i) {
472                             MediaSet set = mediaSets.get(i);
473                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
474                                 if (scanMediaSets) {
475                                     int numItemsLoaded = set.mNumItemsLoaded;
476                                     if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
477                                         dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
478                                         if (set.getNumExpectedItems() == 0) {
479                                             mediaSets.remove(set);
480                                             break;
481                                         }
482                                         if (mListener != null) {
483                                             mListener.onFeedChanged(this, false);
484                                         }
485                                         sleepMs = 100;
486                                         scanMediaSets = false;
487                                     }
488                                 }
489                             } else if (i < bufferedRange.begin || i > bufferedRange.end) {
490                                 // Purge this set to its initial status.
491                                 MediaClustering clustering = mClusterSets.get(set);
492                                 if (clustering != null) {
493                                     clustering.clear();
494                                     mClusterSets.remove(set);
495                                 }
496                                 if (set.getNumItems() != 0)
497                                     set.clear();
498                             }
499                         }
500                     }
501                     if (expandedSetIndex != Shared.INVALID) {
502                         int numSets = mMediaSets.size();
503                         for (int i = 0; i < numSets; ++i) {
504                             // Purge other sets.
505                             if (i != expandedSetIndex) {
506                                 MediaSet set = mediaSets.get(i);
507                                 MediaClustering clustering = mClusterSets.get(set);
508                                 if (clustering != null) {
509                                     clustering.clear();
510                                     mClusterSets.remove(set);
511                                 }
512                                 if (set.getNumItems() != 0)
513                                     set.clear();
514                             }
515                         }
516                         // Make sure all the items are loaded for the album.
517                         int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded;
518                         int requestedItems = mVisibleRange.end;
519                         // requestedItems count changes in clustering mode.
520                         if (mInClusteringMode && mClusterSets != null) {
521                             requestedItems = 0;
522                             MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex));
523                             if (clustering != null) {
524                                 ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
525                                 int numClusters = clusters.size();
526                                 for (int i = 0; i < numClusters; i++) {
527                                     requestedItems += clusters.get(i).getNumExpectedItems();
528                                 }
529                             }
530                         }
531                         MediaSet set = mediaSets.get(expandedSetIndex);
532                         if (numItemsLoaded < set.getNumExpectedItems()) {
533                             // We perform calculations for a window that gets anchored to a multiple of NUM_ITEMS_LOOKAHEAD.
534                             // The start of the window is 0, x, 2x, 3x ... etc where x = NUM_ITEMS_LOOKAHEAD.
535                             dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
536                                     * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
537                             if (set.getNumExpectedItems() == 0) {
538                                 mediaSets.remove(set);
539                                 mListener.onFeedChanged(this, false);
540                             }
541                             if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
542                                 mListener.onFeedChanged(this, false);
543                             }
544                         }
545                     }
546                     MediaFilter filter = mMediaFilter;
547                     if (filter != null && mMediaFilteredSet == null) {
548                         if (expandedSetIndex != Shared.INVALID) {
549                             MediaSet set = mediaSets.get(expandedSetIndex);
550                             ArrayList<MediaItem> items = set.getItems();
551                             int numItems = set.getNumItems();
552                             MediaSet filteredSet = new MediaSet();
553                             filteredSet.setNumExpectedItems(numItems);
554                             mMediaFilteredSet = filteredSet;
555                             for (int i = 0; i < numItems; ++i) {
556                                 MediaItem item = items.get(i);
557                                 if (filter.pass(item)) {
558                                     filteredSet.addItem(item);
559                                 }
560                             }
561                             filteredSet.updateNumExpectedItems();
562                             filteredSet.generateTitle(true);
563                         }
564                         updateListener(true);
565                     }
566                 }
567             }
568         }
569     }
570
571     public void expandMediaSet(int mediaSetIndex) {
572         // We need to check if this slot can be focused or not.
573         if (mListener != null) {
574             mListener.onFeedAboutToChange(this);
575         }
576         if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) {
577             // We are collapsing a previously expanded media set
578             if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) {
579                 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex);
580                 if (set.getNumItems() == 0) {
581                     set.clear();
582                 }
583             }
584         }
585         mExpandedMediaSetIndex = mediaSetIndex;
586         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
587             // Notify Picasa that the user entered the album.
588             // MediaSet set = mMediaSets.get(mediaSetIndex);
589             // PicasaService.requestSync(mContext,
590             // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId);
591         }
592         updateListener(true);
593         mMediaFeedNeedsToRun = true;
594     }
595
596     public boolean canExpandSet(int slotIndex) {
597         int mediaSetIndex = slotIndex;
598         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
599             MediaSet set = mMediaSets.get(mediaSetIndex);
600             if (set.getNumItems() > 0) {
601                 MediaItem item = set.getItems().get(0);
602                 if (item.mId == Shared.INVALID) {
603                     return false;
604                 }
605                 return true;
606             }
607         }
608         return false;
609     }
610
611     public boolean hasExpandedMediaSet() {
612         return (mExpandedMediaSetIndex != Shared.INVALID);
613     }
614
615     public boolean restorePreviousClusteringState() {
616         boolean retVal = disableClusteringIfNecessary();
617         if (retVal) {
618             if (mListener != null) {
619                 mListener.onFeedAboutToChange(this);
620             }
621             updateListener(true);
622             mMediaFeedNeedsToRun = true;
623         }
624         return retVal;
625     }
626
627     private boolean disableClusteringIfNecessary() {
628         if (mInClusteringMode) {
629             // Disable clustering.
630             mInClusteringMode = false;
631             mMediaFeedNeedsToRun = true;
632             return true;
633         }
634         return false;
635     }
636
637     public boolean isClustered() {
638         return mInClusteringMode;
639     }
640
641     public MediaSet getCurrentSet() {
642         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
643             return mMediaSets.get(mExpandedMediaSetIndex);
644         }
645         return null;
646     }
647
648     public void performClustering() {
649         if (mListener != null) {
650             mListener.onFeedAboutToChange(this);
651         }
652         MediaSet setToUse = null;
653         if (mExpandedMediaSetIndex != Shared.INVALID || mExpandedMediaSetIndex < mMediaSets.size()) {
654             setToUse = mMediaSets.get(mExpandedMediaSetIndex);
655         }
656         if (setToUse != null) {
657             MediaClustering clustering = null;
658             synchronized (mClusterSets) {
659                 // Make sure the computation is completed to the end.
660                 clustering = mClusterSets.get(setToUse);
661                 if (clustering != null) {
662                     clustering.compute(null, true);
663                 } else {
664                     return;
665                 }
666             }
667             mInClusteringMode = true;
668             mMediaFeedNeedsToRun = true;
669             updateListener(true);
670         }
671     }
672
673     public void moveSetToFront(MediaSet mediaSet) {
674         ArrayList<MediaSet> mediaSets = mMediaSets;
675         int numSets = mediaSets.size();
676         if (numSets == 0) {
677             mediaSets.add(mediaSet);
678             return;
679         }
680         MediaSet setToFind = mediaSets.get(0);
681         if (setToFind == mediaSet) {
682             return;
683         }
684         mediaSets.set(0, mediaSet);
685         int indexToSwapTill = -1;
686         for (int i = 1; i < numSets; ++i) {
687             MediaSet set = mediaSets.get(i);
688             if (set == mediaSet) {
689                 mediaSets.set(i, setToFind);
690                 indexToSwapTill = i;
691                 break;
692             }
693         }
694         if (indexToSwapTill != Shared.INVALID) {
695             for (int i = indexToSwapTill; i > 1; --i) {
696                 MediaSet setEnd = mediaSets.get(i);
697                 MediaSet setPrev = mediaSets.get(i - 1);
698                 mediaSets.set(i, setPrev);
699                 mediaSets.set(i - 1, setEnd);
700             }
701         }
702         mMediaFeedNeedsToRun = true;
703     }
704
705     public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
706         MediaSet mediaSet = new MediaSet(dataSource);
707         mediaSet.mId = setId;
708         ArrayList<MediaSet> mediaSets = mMediaSets;
709         int numSets = mediaSets.size();
710         for (int i = 0; i < numSets; ++i) {
711             final MediaSet thisSet = mediaSets.get(i);
712             if (thisSet.mId == setId) {
713                 mediaSet.mName = thisSet.mName;
714                 mediaSet.mHasImages = thisSet.mHasImages;
715                 mediaSet.mHasVideos = thisSet.mHasVideos;
716                 mediaSets.set(i, mediaSet);
717                 break;
718             }
719         }
720         mMediaFeedNeedsToRun = true;
721         return mediaSet;
722     }
723
724     public void setSingleImageMode(boolean singleImageMode) {
725         mSingleImageMode = singleImageMode;
726     }
727
728     public boolean isSingleImageMode() {
729         return mSingleImageMode;
730     }
731
732     public MediaSet getExpandedMediaSet() {
733         if (mExpandedMediaSetIndex == Shared.INVALID)
734             return null;
735         if (mExpandedMediaSetIndex >= mMediaSets.size())
736             return null;
737         return mMediaSets.get(mExpandedMediaSetIndex);
738     }
739 }