OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / packages / apps / Gallery3D / src / com / cooliris / media / MediaFeed.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.cooliris.media;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Set;
23
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.database.ContentObserver;
27 import android.util.Log;
28 import android.view.Gravity;
29 import android.widget.Toast;
30 import android.net.Uri;
31 import android.os.Handler;
32 import android.os.Process;
33
34 import com.cooliris.app.App;
35 import com.cooliris.app.Res;
36 import com.cooliris.media.MediaClustering.Cluster;
37
38 public final class MediaFeed implements Runnable {
39     private final String TAG = "MediaFeed";
40     public static final int OPERATION_DELETE = 0;
41     public static final int OPERATION_ROTATE = 1;
42     public static final int OPERATION_CROP = 2;
43
44     private static final int NUM_ITEMS_LOOKAHEAD = 60;
45     private static final int NUM_INTERRUPT_RETRIES = 30;
46     private static final int JOIN_TIMEOUT = 50;
47
48     private IndexRange mVisibleRange = new IndexRange();
49     private IndexRange mBufferedRange = new IndexRange();
50     private ArrayList<MediaSet> mMediaSets = new ArrayList<MediaSet>();
51     private Listener mListener;
52     private DataSource mDataSource;
53     private boolean mListenerNeedsUpdate = false;
54     private boolean mMediaFeedNeedsToRun = false;
55     private MediaSet mSingleWrapper = new MediaSet();
56     private boolean mInClusteringMode = false;
57     private HashMap<MediaSet, MediaClustering> mClusterSets = new HashMap<MediaSet, MediaClustering>(32);
58     private int mExpandedMediaSetIndex = Shared.INVALID;
59     private MediaFilter mMediaFilter;
60     private MediaSet mMediaFilteredSet;
61     private Context mContext;
62     private Thread mDataSourceThread = null;
63     private Thread mAlbumSourceThread = null;
64     private boolean mListenerNeedsLayout;
65     private boolean mWaitingForMediaScanner;
66     private boolean mSingleImageMode;
67     private boolean mLoading;
68     private HashMap<String, ContentObserver> mContentObservers = new HashMap<String, ContentObserver>();
69     private ArrayList<String[]> mRequestedRefresh = new ArrayList<String[]>();
70     private volatile boolean mIsShutdown = false;
71
72     public interface Listener {
73         public abstract void onFeedAboutToChange(MediaFeed feed);
74
75         public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout);
76     }
77
78     public MediaFeed(Context context, DataSource dataSource, Listener listener) {
79         mContext = context;
80         mListener = listener;
81         mDataSource = dataSource;
82         mSingleWrapper.setNumExpectedItems(1);
83         mLoading = true;
84     }
85
86     public void shutdown() {
87         mIsShutdown = true;
88         if (mDataSourceThread != null) {
89             mDataSource.shutdown();
90             repeatShuttingDownThread(mDataSourceThread);
91             mDataSourceThread = null;
92         }
93         if (mAlbumSourceThread != null) {
94             repeatShuttingDownThread(mAlbumSourceThread);
95             mAlbumSourceThread = null;
96         }
97         int numSets = mMediaSets.size();
98         for (int i = 0; i < numSets; ++i) {
99             MediaSet set = mMediaSets.get(i);
100             set.clear();
101         }
102         synchronized (mMediaSets) {
103             mMediaSets.clear();
104         }
105         int numClusters = mClusterSets.size();
106         for (int i = 0; i < numClusters; ++i) {
107             MediaClustering mc = mClusterSets.get(i);
108             if (mc != null) {
109                 mc.clear();
110             }
111         }
112         mClusterSets.clear();
113         mListener = null;
114         mDataSource = null;
115         mSingleWrapper = null;
116     }
117
118     private void repeatShuttingDownThread(Thread targetThread) {
119         for (int i = 0; i < NUM_INTERRUPT_RETRIES && targetThread.isAlive(); ++i) {
120             targetThread.interrupt();
121             try {
122                 targetThread.join(JOIN_TIMEOUT);
123             } catch (InterruptedException e) {
124                 Log.w(TAG, "Cannot stop the thread: " + targetThread.getName(), e);
125                 Thread.currentThread().interrupt();
126                 return;
127             }
128         }
129
130         if (targetThread.isAlive()) {
131             Log.w(TAG, "Cannot stop the thread: " + targetThread.getName());
132         }
133     }
134
135     public void setVisibleRange(int begin, int end) {
136         if (begin != mVisibleRange.begin || end != mVisibleRange.end) {
137             mVisibleRange.begin = begin;
138             mVisibleRange.end = end;
139             int numItems = 96;
140             int numItemsBy2 = numItems / 2;
141             int numItemsBy4 = numItems / 4;
142             mBufferedRange.begin = (begin / numItemsBy2) * numItemsBy2 - numItemsBy4;
143             mBufferedRange.end = mBufferedRange.begin + numItems;
144             mMediaFeedNeedsToRun = true;
145         }
146     }
147
148     public void setFilter(MediaFilter filter) {
149         mMediaFilter = filter;
150         mMediaFilteredSet = null;
151         if (mListener != null) {
152             mListener.onFeedAboutToChange(this);
153         }
154         mMediaFeedNeedsToRun = true;
155     }
156
157     public void removeFilter() {
158         mMediaFilter = null;
159         mMediaFilteredSet = null;
160         if (mListener != null) {
161             mListener.onFeedAboutToChange(this);
162             updateListener(true);
163         }
164         mMediaFeedNeedsToRun = true;
165     }
166
167     public ArrayList<MediaSet> getMediaSets() {
168         return mMediaSets;
169     }
170
171     public MediaSet getMediaSet(final long setId) {
172         if (setId != Shared.INVALID) {
173             try {
174                 int mMediaSetsSize = mMediaSets.size();
175                 for (int i = 0; i < mMediaSetsSize; i++) {
176                     final MediaSet set = mMediaSets.get(i);
177                     if (set.mId == setId) {
178                         set.mFlagForDelete = false;
179                         return set;
180                     }
181                 }
182             } catch (Exception e) {
183                 return null;
184             }
185         }
186         return null;
187     }
188
189     public MediaSet getFilteredSet() {
190         return mMediaFilteredSet;
191     }
192
193     public MediaSet addMediaSet(final long setId, DataSource dataSource) {
194         MediaSet mediaSet = new MediaSet(dataSource);
195         mediaSet.mId = setId;
196         mMediaSets.add(mediaSet);
197         if (mDataSourceThread != null && !mDataSourceThread.isAlive()) {
198             mDataSourceThread.start();
199         }
200         mMediaFeedNeedsToRun = true;
201         return mediaSet;
202     }
203
204     public DataSource getDataSource() {
205         return mDataSource;
206     }
207
208     public MediaClustering getClustering() {
209         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
210             return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex));
211         }
212         return null;
213     }
214
215     public ArrayList<Cluster> getClustersForSet(final MediaSet set) {
216         ArrayList<Cluster> clusters = null;
217         if (mClusterSets != null && mClusterSets.containsKey(set)) {
218             MediaClustering mediaClustering = mClusterSets.get(set);
219             if (mediaClustering != null) {
220                 clusters = mediaClustering.getClusters();
221             }
222         }
223         return clusters;
224     }
225
226     public void addItemToMediaSet(MediaItem item, MediaSet mediaSet) {
227         item.mParentMediaSet = mediaSet;
228         mediaSet.addItem(item);
229         synchronized (mClusterSets) {
230             if (item.mClusteringState == MediaItem.NOT_CLUSTERED) {
231                 MediaClustering clustering = mClusterSets.get(mediaSet);
232                 if (clustering == null) {
233                     clustering = new MediaClustering(mediaSet.isPicassaAlbum());
234                     mClusterSets.put(mediaSet, clustering);
235                 }
236                 clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems());
237                 clustering.addItemForClustering(item);
238                 item.mClusteringState = MediaItem.CLUSTERED;
239             }
240         }
241         mMediaFeedNeedsToRun = true;
242     }
243
244     public void performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
245         int numBuckets = mediaBuckets.size();
246         final ArrayList<MediaBucket> copyMediaBuckets = new ArrayList<MediaBucket>(numBuckets);
247         for (int i = 0; i < numBuckets; ++i) {
248             copyMediaBuckets.add(mediaBuckets.get(i));
249         }
250         if (operation == OPERATION_DELETE && mListener != null) {
251             mListener.onFeedAboutToChange(this);
252         }
253         Thread operationThread = new Thread(new Runnable() {
254             public void run() {
255                 ArrayList<MediaBucket> mediaBuckets = copyMediaBuckets;
256                 if (operation == OPERATION_DELETE) {
257                     int numBuckets = mediaBuckets.size();
258                     for (int i = 0; i < numBuckets; ++i) {
259                         MediaBucket bucket = mediaBuckets.get(i);
260                         MediaSet set = bucket.mediaSet;
261                         ArrayList<MediaItem> items = bucket.mediaItems;
262                         if (set != null && items == null) {
263                             // Remove the entire bucket.
264                             removeMediaSet(set);
265                         } else if (set != null && items != null) {
266                             // We need to remove these items from the set.
267                             int numItems = items.size();
268                             // We also need to delete the items from the
269                             // cluster.
270                             MediaClustering clustering = mClusterSets.get(set);
271                             for (int j = 0; j < numItems; ++j) {
272                                 MediaItem item = items.get(j);
273                                 removeItemFromMediaSet(item, set);
274                                 if (clustering != null) {
275                                     clustering.removeItemFromClustering(item);
276                                 }
277                             }
278                             set.updateNumExpectedItems();
279                             set.generateTitle(true);
280                         }
281                     }
282                     updateListener(true);
283                     mMediaFeedNeedsToRun = true;
284                     if (mDataSource != null) {
285                         mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null);
286                     }
287                 } else {
288                     mDataSource.performOperation(operation, mediaBuckets, data);
289                 }
290             }
291         });
292         operationThread.setName("Operation " + operation);
293         operationThread.start();
294     }
295
296     public void removeMediaSet(MediaSet set) {
297         synchronized (mMediaSets) {
298             mMediaSets.remove(set);
299         }
300         mMediaFeedNeedsToRun = true;
301     }
302
303     private void removeItemFromMediaSet(MediaItem item, MediaSet mediaSet) {
304         mediaSet.removeItem(item);
305         synchronized (mClusterSets) {
306             MediaClustering clustering = mClusterSets.get(mediaSet);
307             if (clustering != null) {
308                 clustering.removeItemFromClustering(item);
309             }
310         }
311         mMediaFeedNeedsToRun = true;
312     }
313
314     public void updateListener(boolean needsLayout) {
315         mListenerNeedsUpdate = true;
316         mListenerNeedsLayout = needsLayout;
317     }
318
319     public int getNumSlots() {
320         int currentMediaSetIndex = mExpandedMediaSetIndex;
321         ArrayList<MediaSet> mediaSets = mMediaSets;
322         int mediaSetsSize = mediaSets.size();
323
324         if (mInClusteringMode == false) {
325             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
326                 return mediaSetsSize;
327             } else {
328                 MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
329                 return setToUse.getNumExpectedItems();
330             }
331         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
332             MediaSet set = mediaSets.get(currentMediaSetIndex);
333             MediaClustering clustering = mClusterSets.get(set);
334             if (clustering != null) {
335                 return clustering.getClustersForDisplay().size();
336             }
337         }
338         return 0;
339     }
340
341     public void copySlotStateFrom(MediaFeed another) {
342         mExpandedMediaSetIndex = another.mExpandedMediaSetIndex;
343         mInClusteringMode = another.mInClusteringMode;
344     }
345
346     public ArrayList<Integer> getBreaks() {
347         if (true)
348             return null;
349         int currentMediaSetIndex = mExpandedMediaSetIndex;
350         ArrayList<MediaSet> mediaSets = mMediaSets;
351         int mediaSetsSize = mediaSets.size();
352         if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize)
353             return null;
354         MediaSet set = mediaSets.get(currentMediaSetIndex);
355         MediaClustering clustering = mClusterSets.get(set);
356         if (clustering != null) {
357             clustering.compute(null, true);
358             final ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
359             int numClusters = clusters.size();
360             final ArrayList<Integer> retVal = new ArrayList<Integer>(numClusters);
361             int size = 0;
362             for (int i = 0; i < numClusters; ++i) {
363                 size += clusters.get(i).getItems().size();
364                 retVal.add(size);
365             }
366             return retVal;
367         } else {
368             return null;
369         }
370     }
371
372     public MediaSet getSetForSlot(int slotIndex) {
373         if (slotIndex < 0) {
374             return null;
375         }
376
377         ArrayList<MediaSet> mediaSets = mMediaSets;
378         int mediaSetsSize = mediaSets.size();
379         int currentMediaSetIndex = mExpandedMediaSetIndex;
380
381         if (mInClusteringMode == false) {
382             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
383                 if (slotIndex >= mediaSetsSize) {
384                     return null;
385                 }
386                 return mMediaSets.get(slotIndex);
387             }
388             if (mSingleWrapper.getNumItems() == 0) {
389                 mSingleWrapper.addItem(null);
390             }
391             MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
392             ArrayList<MediaItem> items = setToUse.getItems();
393             if (slotIndex >= setToUse.getNumItems()) {
394                 return null;
395             }
396             mSingleWrapper.getItems().set(0, items.get(slotIndex));
397             return mSingleWrapper;
398         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
399             MediaSet set = mediaSets.get(currentMediaSetIndex);
400             MediaClustering clustering = mClusterSets.get(set);
401             if (clustering != null) {
402                 ArrayList<MediaClustering.Cluster> clusters = clustering.getClustersForDisplay();
403                 if (clusters.size() > slotIndex) {
404                     MediaClustering.Cluster cluster = clusters.get(slotIndex);
405                     cluster.generateCaption(mContext);
406                     return cluster;
407                 }
408             }
409         }
410         return null;
411     }
412
413     public boolean getWaitingForMediaScanner() {
414         return mWaitingForMediaScanner;
415     }
416
417     public boolean isLoading() {
418         return mLoading;
419     }
420
421     public void start() {
422         final MediaFeed feed = this;
423         onResume();
424         mLoading = true;
425         mDataSourceThread = new Thread(this);
426         mDataSourceThread.setName("MediaFeed");
427         mIsShutdown = false;
428         mAlbumSourceThread = new Thread(new Runnable() {
429             public void run() {
430                 if (mContext == null)
431                     return;
432                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
433                 DataSource dataSource = mDataSource;
434                 // We must wait while the SD card is mounted or the MediaScanner
435                 // is running.
436                 if (dataSource != null) {
437                     loadMediaSets();
438                 }
439                 mWaitingForMediaScanner = false;
440                 while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) {
441                     // MediaScanner is still running, wait
442                     if (Thread.interrupted())
443                         return;
444                     mWaitingForMediaScanner = true;
445                     try {
446                         if (mContext == null)
447                             return;
448                         showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG);
449                         if (dataSource != null) {
450                             loadMediaSets();
451                         }
452                         Thread.sleep(10000);
453                     } catch (InterruptedException e) {
454                         return;
455                     }
456                 }
457                 if (mWaitingForMediaScanner) {
458                     showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG);
459                     mWaitingForMediaScanner = false;
460                     loadMediaSets();
461                 }
462                 mLoading = false;
463             }
464         });
465         mAlbumSourceThread.setName("MediaSets");
466         mAlbumSourceThread.start();
467     }
468
469     private void loadMediaSets() {
470         if (mDataSource == null)
471             return;
472         final ArrayList<MediaSet> sets = mMediaSets;
473         synchronized (sets) {
474             final int numSets = sets.size();
475             for (int i = 0; i < numSets; ++i) {
476                 final MediaSet set = sets.get(i);
477                 set.mFlagForDelete = true;
478             }
479             mDataSource.refresh(MediaFeed.this, mDataSource.getDatabaseUris());
480             mDataSource.loadMediaSets(MediaFeed.this);
481             final ArrayList<MediaSet> setsToRemove = new ArrayList<MediaSet>();
482             for (int i = 0; i < numSets; ++i) {
483                 final MediaSet set = sets.get(i);
484                 if (set.mFlagForDelete) {
485                     setsToRemove.add(set);
486                 }
487             }
488             int numSetsToRemove = setsToRemove.size();
489             for (int i = 0; i < numSetsToRemove; ++i) {
490                 sets.remove(setsToRemove.get(i));
491             }
492             setsToRemove.clear();
493         }
494         mMediaFeedNeedsToRun = true;
495         updateListener(false);
496     }
497
498     private void showToast(final String string, final int duration) {
499         showToast(string, duration, false);
500     }
501
502     private void showToast(final String string, final int duration, final boolean centered) {
503         if (mContext != null && !App.get(mContext).isPaused()) {
504             App.get(mContext).getHandler().post(new Runnable() {
505                 public void run() {
506                     if (mContext != null) {
507                         Toast toast = Toast.makeText(mContext, string, duration);
508                         if (centered) {
509                             toast.setGravity(Gravity.CENTER, 0, 0);
510                         }
511                         toast.show();
512                     }
513                 }
514             });
515         }
516     }
517
518     public void run() {
519         DataSource dataSource = mDataSource;
520         int sleepMs = 10;
521         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
522         if (dataSource != null) {
523             while (!Thread.interrupted() && !mIsShutdown) {
524                 String[] databaseUris = null;
525                 boolean performRefresh = false;
526                 synchronized (mRequestedRefresh) {
527                     if (mRequestedRefresh.size() > 0) {
528                         // We prune this first.
529                         int numRequests = mRequestedRefresh.size();
530                         for (int i = 0; i < numRequests; ++i) {
531                             databaseUris = ArrayUtils.addAll(databaseUris, mRequestedRefresh.get(i));
532                         }
533                         mRequestedRefresh.clear();
534                         performRefresh = true;
535                         // We need to eliminate duplicate uris in this array
536                         final HashMap<String, String> uris = new HashMap<String, String>();
537                         if (databaseUris != null) {
538                             int numUris = databaseUris.length;
539                             for (int i = 0; i < numUris; ++i) {
540                                 final String uri = databaseUris[i];
541                                 if (uri != null)
542                                     uris.put(uri, uri);
543                             }
544                         }
545                         databaseUris = new String[0];
546                         databaseUris = (String[]) uris.keySet().toArray(databaseUris);
547                     }
548                 }
549                 boolean settingFeedAboutToChange = false;
550                 if (performRefresh) {
551                     if (dataSource != null) {
552                         if (mListener != null) {
553                             settingFeedAboutToChange = true;
554                             mListener.onFeedAboutToChange(this);
555                         }
556                         dataSource.refresh(this, databaseUris);
557                         mMediaFeedNeedsToRun = true;
558                     }
559                 }
560                 if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) {
561                     mListenerNeedsUpdate = false;
562                     if (mListener != null)
563                         synchronized (mMediaSets) {
564                             mListener.onFeedChanged(this, mListenerNeedsLayout);
565                         }
566                     try {
567                         Thread.sleep(sleepMs);
568                     } catch (InterruptedException e) {
569                         return;
570                     }
571                 } else {
572                     try {
573                         Thread.sleep(sleepMs);
574                     } catch (InterruptedException e) {
575                         return;
576                     }
577                 }
578                 sleepMs = 300;
579                 if (!mMediaFeedNeedsToRun)
580                     continue;
581                 App app = App.get(mContext);
582                 if (app == null || app.isPaused())
583                     continue;
584                 if (settingFeedAboutToChange) {
585                     updateListener(true);
586                 }
587                 mMediaFeedNeedsToRun = false;
588                 ArrayList<MediaSet> mediaSets = mMediaSets;
589                 synchronized (mediaSets) {
590                     int expandedSetIndex = mExpandedMediaSetIndex;
591                     if (expandedSetIndex >= mMediaSets.size()) {
592                         expandedSetIndex = Shared.INVALID;
593                     }
594                     if (expandedSetIndex == Shared.INVALID) {
595                         // We purge the sets outside this visibleRange.
596                         int numSets = mediaSets.size();
597                         IndexRange visibleRange = mVisibleRange;
598                         IndexRange bufferedRange = mBufferedRange;
599                         boolean scanMediaSets = true;
600                         for (int i = 0; i < numSets; ++i) {
601                             if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) {
602                                 MediaSet set = mediaSets.get(i);
603                                 int numItemsLoaded = set.mNumItemsLoaded;
604                                 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
605                                     synchronized (set) {
606                                         dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
607                                         set.checkForDeletedItems();
608                                     }
609                                     if (set.getNumExpectedItems() == 0) {
610                                         mediaSets.remove(set);
611                                         break;
612                                     }
613                                     if (mListener != null) {
614                                         mListenerNeedsUpdate = false;
615                                         mListener.onFeedChanged(this, mListenerNeedsLayout);
616                                         mListenerNeedsLayout = false;
617                                     }
618                                     sleepMs = 100;
619                                     scanMediaSets = false;
620                                 }
621                                 if (!set.setContainsValidItems()) {
622                                     mediaSets.remove(set);
623                                     if (mListener != null) {
624                                         mListenerNeedsUpdate = false;
625                                         mListener.onFeedChanged(this, mListenerNeedsLayout);
626                                         mListenerNeedsLayout = false;
627                                     }
628                                     break;
629                                 }
630                             }
631                         }
632                         numSets = mediaSets.size();
633                         for (int i = 0; i < numSets; ++i) {
634                             MediaSet set = mediaSets.get(i);
635                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
636                                 if (scanMediaSets) {
637                                     int numItemsLoaded = set.mNumItemsLoaded;
638                                     if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
639                                         synchronized (set) {
640                                             dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
641                                             set.checkForDeletedItems();
642                                         }
643                                         if (set.getNumExpectedItems() == 0) {
644                                             mediaSets.remove(set);
645                                             break;
646                                         }
647                                         if (mListener != null) {
648                                             mListenerNeedsUpdate = false;
649                                             mListener.onFeedChanged(this, mListenerNeedsLayout);
650                                             mListenerNeedsLayout = false;
651                                         }
652                                         sleepMs = 100;
653                                         scanMediaSets = false;
654                                     }
655                                 }
656                             } else if (!mListenerNeedsUpdate && (i < bufferedRange.begin || i > bufferedRange.end)) {
657                                 // Purge this set to its initial status.
658                                 MediaClustering clustering = mClusterSets.get(set);
659                                 if (clustering != null) {
660                                     clustering.clear();
661                                     mClusterSets.remove(set);
662                                 }
663                                 if (set.getNumItems() != 0)
664                                     set.clear();
665                             }
666                         }
667                     }
668                     if (expandedSetIndex != Shared.INVALID) {
669                         int numSets = mMediaSets.size();
670                         for (int i = 0; i < numSets; ++i) {
671                             // Purge other sets.
672                             if (i != expandedSetIndex) {
673                                 MediaSet set = mediaSets.get(i);
674                                 MediaClustering clustering = mClusterSets.get(set);
675                                 if (clustering != null) {
676                                     clustering.clear();
677                                     mClusterSets.remove(set);
678                                 }
679                                 if (set.mNumItemsLoaded != 0)
680                                     set.clear();
681                             }
682                         }
683                         // Make sure all the items are loaded for the album.
684                         int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded;
685                         int requestedItems = mVisibleRange.end;
686                         // requestedItems count changes in clustering mode.
687                         if (mInClusteringMode && mClusterSets != null) {
688                             requestedItems = 0;
689                             MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex));
690                             if (clustering != null) {
691                                 ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
692                                 int numClusters = clusters.size();
693                                 for (int i = 0; i < numClusters; i++) {
694                                     requestedItems += clusters.get(i).getNumExpectedItems();
695                                 }
696                             }
697                         }
698                         MediaSet set = mediaSets.get(expandedSetIndex);
699                         if (numItemsLoaded < set.getNumExpectedItems()) {
700                             // We perform calculations for a window that gets
701                             // anchored to a multiple of NUM_ITEMS_LOOKAHEAD.
702                             // The start of the window is 0, x, 2x, 3x ... etc
703                             // where x = NUM_ITEMS_LOOKAHEAD.
704                             synchronized (set) {
705                                 dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
706                                         * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
707                                 set.checkForDeletedItems();
708                             }
709                             if (set.getNumExpectedItems() == 0) {
710                                 mediaSets.remove(set);
711                                 mListenerNeedsUpdate = false;
712                                 mListener.onFeedChanged(this, mListenerNeedsLayout);
713                                 mListenerNeedsLayout = false;
714                             }
715                             if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
716                                 mListenerNeedsUpdate = false;
717                                 mListener.onFeedChanged(this, mListenerNeedsLayout);
718                                 mListenerNeedsLayout = false;
719                             }
720                         }
721                     }
722                     MediaFilter filter = mMediaFilter;
723                     if (filter != null && mMediaFilteredSet == null) {
724                         if (expandedSetIndex != Shared.INVALID) {
725                             MediaSet set = mediaSets.get(expandedSetIndex);
726                             ArrayList<MediaItem> items = set.getItems();
727                             int numItems = set.getNumItems();
728                             MediaSet filteredSet = new MediaSet();
729                             filteredSet.setNumExpectedItems(numItems);
730                             mMediaFilteredSet = filteredSet;
731                             for (int i = 0; i < numItems; ++i) {
732                                 MediaItem item = items.get(i);
733                                 if (filter.pass(item)) {
734                                     filteredSet.addItem(item);
735                                 }
736                             }
737                             filteredSet.updateNumExpectedItems();
738                             filteredSet.generateTitle(true);
739                         }
740                         updateListener(true);
741                     }
742                 }
743             }
744         }
745     }
746
747     public void expandMediaSet(int mediaSetIndex) {
748         // We need to check if this slot can be focused or not.
749         if (mListener != null) {
750             mListener.onFeedAboutToChange(this);
751         }
752         if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) {
753             // We are collapsing a previously expanded media set
754             if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) {
755                 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex);
756                 if (set.getNumItems() == 0) {
757                     set.clear();
758                 }
759             }
760         }
761         mExpandedMediaSetIndex = mediaSetIndex;
762         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
763             // Notify Picasa that the user entered the album.
764             // MediaSet set = mMediaSets.get(mediaSetIndex);
765             // PicasaService.requestSync(mContext,
766             // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId);
767         }
768         updateListener(true);
769         mMediaFeedNeedsToRun = true;
770     }
771
772     public boolean canExpandSet(int slotIndex) {
773         int mediaSetIndex = slotIndex;
774         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
775             MediaSet set = mMediaSets.get(mediaSetIndex);
776             if (set.getNumItems() > 0) {
777                 MediaItem item = set.getItems().get(0);
778                 if (item.mId == Shared.INVALID) {
779                     return false;
780                 }
781                 return true;
782             }
783         }
784         return false;
785     }
786
787     public boolean hasExpandedMediaSet() {
788         return (mExpandedMediaSetIndex != Shared.INVALID);
789     }
790
791     public boolean restorePreviousClusteringState() {
792         boolean retVal = disableClusteringIfNecessary();
793         if (retVal) {
794             if (mListener != null) {
795                 mListener.onFeedAboutToChange(this);
796             }
797             updateListener(true);
798             mMediaFeedNeedsToRun = true;
799         }
800         return retVal;
801     }
802
803     private boolean disableClusteringIfNecessary() {
804         if (mInClusteringMode) {
805             // Disable clustering.
806             mInClusteringMode = false;
807             mMediaFeedNeedsToRun = true;
808             return true;
809         }
810         return false;
811     }
812
813     public boolean isClustered() {
814         return mInClusteringMode;
815     }
816
817     public MediaSet getCurrentSet() {
818         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
819             return mMediaSets.get(mExpandedMediaSetIndex);
820         }
821         return null;
822     }
823
824     public void performClustering() {
825         if (mListener != null) {
826             mListener.onFeedAboutToChange(this);
827         }
828         MediaSet setToUse = null;
829         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
830             setToUse = mMediaSets.get(mExpandedMediaSetIndex);
831         }
832         if (setToUse != null) {
833             MediaClustering clustering = null;
834             synchronized (mClusterSets) {
835                 // Make sure the computation is completed to the end.
836                 clustering = mClusterSets.get(setToUse);
837                 if (clustering != null) {
838                     clustering.compute(null, true);
839                 } else {
840                     return;
841                 }
842             }
843             mInClusteringMode = true;
844             updateListener(true);
845         }
846     }
847
848     public void moveSetToFront(MediaSet mediaSet) {
849         ArrayList<MediaSet> mediaSets = mMediaSets;
850         int numSets = mediaSets.size();
851         if (numSets == 0) {
852             mediaSets.add(mediaSet);
853             return;
854         }
855         MediaSet setToFind = mediaSets.get(0);
856         if (setToFind == mediaSet) {
857             return;
858         }
859         mediaSets.set(0, mediaSet);
860         int indexToSwapTill = -1;
861         for (int i = 1; i < numSets; ++i) {
862             MediaSet set = mediaSets.get(i);
863             if (set == mediaSet) {
864                 mediaSets.set(i, setToFind);
865                 indexToSwapTill = i;
866                 break;
867             }
868         }
869         if (indexToSwapTill != Shared.INVALID) {
870             for (int i = indexToSwapTill; i > 1; --i) {
871                 MediaSet setEnd = mediaSets.get(i);
872                 MediaSet setPrev = mediaSets.get(i - 1);
873                 mediaSets.set(i, setPrev);
874                 mediaSets.set(i - 1, setEnd);
875             }
876         }
877         mMediaFeedNeedsToRun = true;
878     }
879
880     public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
881         Log.i(TAG, "Replacing media set " + setId);
882         final MediaSet set = getMediaSet(setId);
883         if (set != null)
884             set.refresh();
885         return set;
886     }
887
888     public void setSingleImageMode(boolean singleImageMode) {
889         mSingleImageMode = singleImageMode;
890     }
891
892     public boolean isSingleImageMode() {
893         return mSingleImageMode;
894     }
895
896     public MediaSet getExpandedMediaSet() {
897         if (mExpandedMediaSetIndex == Shared.INVALID)
898             return null;
899         if (mExpandedMediaSetIndex >= mMediaSets.size())
900             return null;
901         return mMediaSets.get(mExpandedMediaSetIndex);
902     }
903
904     public void refresh() {
905         if (mDataSource != null) {
906             synchronized (mRequestedRefresh) {
907                 mRequestedRefresh.add(mDataSource.getDatabaseUris());
908             }
909         }
910     }
911
912     private void refresh(final String[] databaseUris) {
913         synchronized (mMediaSets) {
914             if (mDataSource != null) {
915                 synchronized (mRequestedRefresh) {
916                     mRequestedRefresh.add(databaseUris);
917                 }
918             }
919         }
920     }
921
922     public void onPause() {
923         final HashMap<String, ContentObserver> observers = mContentObservers;
924         final int numObservers = observers.size();
925         if (numObservers > 0) {
926             String[] uris = new String[numObservers];
927             final Set<String> keySet = observers.keySet();
928             if (keySet != null) {
929                 uris = keySet.toArray(uris);
930                 final int numUris = uris.length;
931                 final ContentResolver cr = mContext.getContentResolver();
932                 for (int i = 0; i < numUris; ++i) {
933                     final String uri = uris[i];
934                     if (uri != null) {
935                         final ContentObserver observer = observers.get(uri);
936                         cr.unregisterContentObserver(observer);
937                         observers.remove(uri);
938                     }
939                 }
940             }
941         }
942         observers.clear();
943     }
944
945     public void onResume() {
946         final Context context = mContext;
947         final DataSource dataSource = mDataSource;
948         if (context == null || dataSource == null)
949             return;
950         // We setup the listeners for this datasource
951         final String[] uris = dataSource.getDatabaseUris();
952         final HashMap<String, ContentObserver> observers = mContentObservers;
953         if (context instanceof Gallery) {
954             final Gallery gallery = (Gallery) context;
955             final ContentResolver cr = context.getContentResolver();
956             if (uris != null) {
957                 final int numUris = uris.length;
958                 for (int i = 0; i < numUris; ++i) {
959                     final String uri = uris[i];
960                     final ContentObserver presentObserver = observers.get(uri);
961                     if (presentObserver == null) {
962                         final Handler handler = App.get(context).getHandler();
963                         final ContentObserver observer = new ContentObserver(handler) {
964                             public void onChange(boolean selfChange) {
965                                 if (!mWaitingForMediaScanner) {
966                                     MediaFeed.this.refresh(new String[] { uri });
967                                 }
968                             }
969                         };
970                         cr.registerContentObserver(Uri.parse(uri), true, observer);
971                         observers.put(uri, observer);
972                     }
973                 }
974             }
975         }
976         refresh();
977     }
978 }