• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onFeedAboutToChange(MediaFeed feed)73         public abstract void onFeedAboutToChange(MediaFeed feed);
74 
onFeedChanged(MediaFeed feed, boolean needsLayout)75         public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout);
76     }
77 
MediaFeed(Context context, DataSource dataSource, Listener listener)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 
shutdown()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 
repeatShuttingDownThread(Thread targetThread)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 
setVisibleRange(int begin, int end)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 
setFilter(MediaFilter filter)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 
removeFilter()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 
getMediaSets()167     public ArrayList<MediaSet> getMediaSets() {
168         return mMediaSets;
169     }
170 
getMediaSet(final long setId)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 
getFilteredSet()189     public MediaSet getFilteredSet() {
190         return mMediaFilteredSet;
191     }
192 
addMediaSet(final long setId, DataSource dataSource)193     public MediaSet addMediaSet(final long setId, DataSource dataSource) {
194         int numSets = mMediaSets.size();
195         for (int i = 0; i < numSets; i++) {
196             MediaSet set = mMediaSets.get(i);
197             if ((set.mId == setId) && (set.mDataSource == dataSource)) {
198                 // The mediaset already exists, but might be out-dated.
199                 // To avoid the same mediaset being added twice, we delete
200                 // the old one first, then add the new one below.
201                 mMediaSets.remove(i);
202                 break;
203             }
204         }
205 
206         MediaSet mediaSet = new MediaSet(dataSource);
207         mediaSet.mId = setId;
208         mMediaSets.add(mediaSet);
209         if (mDataSourceThread != null && !mDataSourceThread.isAlive()) {
210             mDataSourceThread.start();
211         }
212         mMediaFeedNeedsToRun = true;
213         return mediaSet;
214     }
215 
getDataSource()216     public DataSource getDataSource() {
217         return mDataSource;
218     }
219 
getClustering()220     public MediaClustering getClustering() {
221         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
222             return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex));
223         }
224         return null;
225     }
226 
getClustersForSet(final MediaSet set)227     public ArrayList<Cluster> getClustersForSet(final MediaSet set) {
228         ArrayList<Cluster> clusters = null;
229         if (mClusterSets != null && mClusterSets.containsKey(set)) {
230             MediaClustering mediaClustering = mClusterSets.get(set);
231             if (mediaClustering != null) {
232                 clusters = mediaClustering.getClusters();
233             }
234         }
235         return clusters;
236     }
237 
addItemToMediaSet(MediaItem item, MediaSet mediaSet)238     public void addItemToMediaSet(MediaItem item, MediaSet mediaSet) {
239         item.mParentMediaSet = mediaSet;
240         mediaSet.addItem(item);
241         synchronized (mClusterSets) {
242             if (item.mClusteringState == MediaItem.NOT_CLUSTERED) {
243                 MediaClustering clustering = mClusterSets.get(mediaSet);
244                 if (clustering == null) {
245                     clustering = new MediaClustering(mediaSet.isPicassaAlbum());
246                     mClusterSets.put(mediaSet, clustering);
247                 }
248                 clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems());
249                 clustering.addItemForClustering(item);
250                 item.mClusteringState = MediaItem.CLUSTERED;
251             }
252         }
253         mMediaFeedNeedsToRun = true;
254     }
255 
performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data)256     public void performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
257         int numBuckets = mediaBuckets.size();
258         final ArrayList<MediaBucket> copyMediaBuckets = new ArrayList<MediaBucket>(numBuckets);
259         for (int i = 0; i < numBuckets; ++i) {
260             copyMediaBuckets.add(mediaBuckets.get(i));
261         }
262         if (operation == OPERATION_DELETE && mListener != null) {
263             mListener.onFeedAboutToChange(this);
264         }
265         Thread operationThread = new Thread(new Runnable() {
266             public void run() {
267                 ArrayList<MediaBucket> mediaBuckets = copyMediaBuckets;
268                 if (operation == OPERATION_DELETE) {
269                     int numBuckets = mediaBuckets.size();
270                     for (int i = 0; i < numBuckets; ++i) {
271                         MediaBucket bucket = mediaBuckets.get(i);
272                         MediaSet set = bucket.mediaSet;
273                         ArrayList<MediaItem> items = bucket.mediaItems;
274                         if (set != null && items == null) {
275                             // Remove the entire bucket.
276                             removeMediaSet(set);
277                         } else if (set != null && items != null) {
278                             // We need to remove these items from the set.
279                             int numItems = items.size();
280                             // We also need to delete the items from the
281                             // cluster.
282                             MediaClustering clustering = mClusterSets.get(set);
283                             for (int j = 0; j < numItems; ++j) {
284                                 MediaItem item = items.get(j);
285                                 removeItemFromMediaSet(item, set);
286                                 if (clustering != null) {
287                                     clustering.removeItemFromClustering(item);
288                                 }
289                             }
290                             set.updateNumExpectedItems();
291                             set.generateTitle(true);
292                         }
293                     }
294                     updateListener(true);
295                     mMediaFeedNeedsToRun = true;
296                     if (mDataSource != null) {
297                         mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null);
298                     }
299                 } else {
300                     mDataSource.performOperation(operation, mediaBuckets, data);
301                 }
302             }
303         });
304         operationThread.setName("Operation " + operation);
305         operationThread.start();
306     }
307 
removeMediaSet(MediaSet set)308     public void removeMediaSet(MediaSet set) {
309         synchronized (mMediaSets) {
310             mMediaSets.remove(set);
311         }
312         mMediaFeedNeedsToRun = true;
313     }
314 
removeItemFromMediaSet(MediaItem item, MediaSet mediaSet)315     private void removeItemFromMediaSet(MediaItem item, MediaSet mediaSet) {
316         mediaSet.removeItem(item);
317         synchronized (mClusterSets) {
318             MediaClustering clustering = mClusterSets.get(mediaSet);
319             if (clustering != null) {
320                 clustering.removeItemFromClustering(item);
321             }
322         }
323         mMediaFeedNeedsToRun = true;
324     }
325 
updateListener(boolean needsLayout)326     public void updateListener(boolean needsLayout) {
327         mListenerNeedsUpdate = true;
328         mListenerNeedsLayout = needsLayout;
329     }
330 
getNumSlots()331     public int getNumSlots() {
332         int currentMediaSetIndex = mExpandedMediaSetIndex;
333         ArrayList<MediaSet> mediaSets = mMediaSets;
334         int mediaSetsSize = mediaSets.size();
335 
336         if (mInClusteringMode == false) {
337             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
338                 return mediaSetsSize;
339             } else {
340                 MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
341                 return setToUse.getNumExpectedItems();
342             }
343         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
344             MediaSet set = mediaSets.get(currentMediaSetIndex);
345             MediaClustering clustering = mClusterSets.get(set);
346             if (clustering != null) {
347                 return clustering.getClustersForDisplay().size();
348             }
349         }
350         return 0;
351     }
352 
copySlotStateFrom(MediaFeed another)353     public void copySlotStateFrom(MediaFeed another) {
354         mExpandedMediaSetIndex = another.mExpandedMediaSetIndex;
355         mInClusteringMode = another.mInClusteringMode;
356     }
357 
getBreaks()358     public ArrayList<Integer> getBreaks() {
359         if (true)
360             return null;
361         int currentMediaSetIndex = mExpandedMediaSetIndex;
362         ArrayList<MediaSet> mediaSets = mMediaSets;
363         int mediaSetsSize = mediaSets.size();
364         if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize)
365             return null;
366         MediaSet set = mediaSets.get(currentMediaSetIndex);
367         MediaClustering clustering = mClusterSets.get(set);
368         if (clustering != null) {
369             clustering.compute(null, true);
370             final ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
371             int numClusters = clusters.size();
372             final ArrayList<Integer> retVal = new ArrayList<Integer>(numClusters);
373             int size = 0;
374             for (int i = 0; i < numClusters; ++i) {
375                 size += clusters.get(i).getItems().size();
376                 retVal.add(size);
377             }
378             return retVal;
379         } else {
380             return null;
381         }
382     }
383 
getSetForSlot(int slotIndex)384     public MediaSet getSetForSlot(int slotIndex) {
385         if (slotIndex < 0) {
386             return null;
387         }
388 
389         ArrayList<MediaSet> mediaSets = mMediaSets;
390         int mediaSetsSize = mediaSets.size();
391         int currentMediaSetIndex = mExpandedMediaSetIndex;
392 
393         if (mInClusteringMode == false) {
394             if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
395                 if (slotIndex >= mediaSetsSize) {
396                     return null;
397                 }
398                 return mMediaSets.get(slotIndex);
399             }
400             if (mSingleWrapper.getNumItems() == 0) {
401                 mSingleWrapper.addItem(null);
402             }
403             MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
404             ArrayList<MediaItem> items = setToUse.getItems();
405             if (slotIndex >= setToUse.getNumItems()) {
406                 return null;
407             }
408             mSingleWrapper.getItems().set(0, items.get(slotIndex));
409             return mSingleWrapper;
410         } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) {
411             MediaSet set = mediaSets.get(currentMediaSetIndex);
412             MediaClustering clustering = mClusterSets.get(set);
413             if (clustering != null) {
414                 ArrayList<MediaClustering.Cluster> clusters = clustering.getClustersForDisplay();
415                 if (clusters.size() > slotIndex) {
416                     MediaClustering.Cluster cluster = clusters.get(slotIndex);
417                     cluster.generateCaption(mContext);
418                     return cluster;
419                 }
420             }
421         }
422         return null;
423     }
424 
getWaitingForMediaScanner()425     public boolean getWaitingForMediaScanner() {
426         return mWaitingForMediaScanner;
427     }
428 
isLoading()429     public boolean isLoading() {
430         return mLoading;
431     }
432 
start()433     public void start() {
434         final MediaFeed feed = this;
435         onResume();
436         mLoading = true;
437         mDataSourceThread = new Thread(this);
438         mDataSourceThread.setName("MediaFeed");
439         mIsShutdown = false;
440         mAlbumSourceThread = new Thread(new Runnable() {
441             public void run() {
442                 if (mContext == null)
443                     return;
444                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
445                 DataSource dataSource = mDataSource;
446                 // We must wait while the SD card is mounted or the MediaScanner
447                 // is running.
448                 if (dataSource != null) {
449                     loadMediaSets();
450                 }
451                 mWaitingForMediaScanner = false;
452                 while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) {
453                     // MediaScanner is still running, wait
454                     if (Thread.interrupted())
455                         return;
456                     mWaitingForMediaScanner = true;
457                     try {
458                         if (mContext == null)
459                             return;
460                         showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG);
461                         if (dataSource != null) {
462                             loadMediaSets();
463                         }
464                         Thread.sleep(10000);
465                     } catch (InterruptedException e) {
466                         return;
467                     }
468                 }
469                 if (mWaitingForMediaScanner) {
470                     showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG);
471                     mWaitingForMediaScanner = false;
472                     loadMediaSets();
473                 }
474                 mLoading = false;
475             }
476         });
477         mAlbumSourceThread.setName("MediaSets");
478         mAlbumSourceThread.start();
479     }
480 
loadMediaSets()481     private void loadMediaSets() {
482         if (mDataSource == null)
483             return;
484         final ArrayList<MediaSet> sets = mMediaSets;
485         synchronized (sets) {
486             final int numSets = sets.size();
487             for (int i = 0; i < numSets; ++i) {
488                 final MediaSet set = sets.get(i);
489                 set.mFlagForDelete = true;
490             }
491             mDataSource.refresh(MediaFeed.this, mDataSource.getDatabaseUris());
492             mDataSource.loadMediaSets(MediaFeed.this);
493             final ArrayList<MediaSet> setsToRemove = new ArrayList<MediaSet>();
494             for (int i = 0; i < numSets; ++i) {
495                 final MediaSet set = sets.get(i);
496                 if (set.mFlagForDelete) {
497                     setsToRemove.add(set);
498                 }
499             }
500             int numSetsToRemove = setsToRemove.size();
501             for (int i = 0; i < numSetsToRemove; ++i) {
502                 sets.remove(setsToRemove.get(i));
503             }
504             setsToRemove.clear();
505         }
506         mMediaFeedNeedsToRun = true;
507         updateListener(false);
508     }
509 
showToast(final String string, final int duration)510     private void showToast(final String string, final int duration) {
511         showToast(string, duration, false);
512     }
513 
showToast(final String string, final int duration, final boolean centered)514     private void showToast(final String string, final int duration, final boolean centered) {
515         if (mContext != null && !App.get(mContext).isPaused()) {
516             App.get(mContext).getHandler().post(new Runnable() {
517                 public void run() {
518                     if (mContext != null) {
519                         Toast toast = Toast.makeText(mContext, string, duration);
520                         if (centered) {
521                             toast.setGravity(Gravity.CENTER, 0, 0);
522                         }
523                         toast.show();
524                     }
525                 }
526             });
527         }
528     }
529 
run()530     public void run() {
531         DataSource dataSource = mDataSource;
532         int sleepMs = 10;
533         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
534         if (dataSource != null) {
535             while (!Thread.interrupted() && !mIsShutdown) {
536                 String[] databaseUris = null;
537                 boolean performRefresh = false;
538                 synchronized (mRequestedRefresh) {
539                     if (mRequestedRefresh.size() > 0) {
540                         // We prune this first.
541                         int numRequests = mRequestedRefresh.size();
542                         for (int i = 0; i < numRequests; ++i) {
543                             databaseUris = ArrayUtils.addAll(databaseUris, mRequestedRefresh.get(i));
544                         }
545                         mRequestedRefresh.clear();
546                         performRefresh = true;
547                         // We need to eliminate duplicate uris in this array
548                         final HashMap<String, String> uris = new HashMap<String, String>();
549                         if (databaseUris != null) {
550                             int numUris = databaseUris.length;
551                             for (int i = 0; i < numUris; ++i) {
552                                 final String uri = databaseUris[i];
553                                 if (uri != null)
554                                     uris.put(uri, uri);
555                             }
556                         }
557                         databaseUris = new String[0];
558                         databaseUris = (String[]) uris.keySet().toArray(databaseUris);
559                     }
560                 }
561                 boolean settingFeedAboutToChange = false;
562                 if (performRefresh) {
563                     if (dataSource != null) {
564                         if (mListener != null) {
565                             settingFeedAboutToChange = true;
566                             mListener.onFeedAboutToChange(this);
567                         }
568                         dataSource.refresh(this, databaseUris);
569                         mMediaFeedNeedsToRun = true;
570                     }
571                 }
572                 if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) {
573                     mListenerNeedsUpdate = false;
574                     if (mListener != null)
575                         synchronized (mMediaSets) {
576                             mListener.onFeedChanged(this, mListenerNeedsLayout);
577                         }
578                     try {
579                         Thread.sleep(sleepMs);
580                     } catch (InterruptedException e) {
581                         return;
582                     }
583                 } else {
584                     try {
585                         Thread.sleep(sleepMs);
586                     } catch (InterruptedException e) {
587                         return;
588                     }
589                 }
590                 sleepMs = 300;
591                 if (!mMediaFeedNeedsToRun)
592                     continue;
593                 App app = App.get(mContext);
594                 if (app == null || app.isPaused())
595                     continue;
596                 if (settingFeedAboutToChange) {
597                     updateListener(true);
598                 }
599                 mMediaFeedNeedsToRun = false;
600                 ArrayList<MediaSet> mediaSets = mMediaSets;
601                 synchronized (mediaSets) {
602                     int expandedSetIndex = mExpandedMediaSetIndex;
603                     if (expandedSetIndex >= mMediaSets.size()) {
604                         expandedSetIndex = Shared.INVALID;
605                     }
606                     if (expandedSetIndex == Shared.INVALID) {
607                         // We purge the sets outside this visibleRange.
608                         int numSets = mediaSets.size();
609                         IndexRange visibleRange = mVisibleRange;
610                         IndexRange bufferedRange = mBufferedRange;
611                         boolean scanMediaSets = true;
612                         for (int i = 0; i < numSets; ++i) {
613                             if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) {
614                                 MediaSet set = mediaSets.get(i);
615                                 int numItemsLoaded = set.mNumItemsLoaded;
616                                 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
617                                     synchronized (set) {
618                                         dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
619                                         set.checkForDeletedItems();
620                                     }
621                                     if (set.getNumExpectedItems() == 0) {
622                                         mediaSets.remove(set);
623                                         break;
624                                     }
625                                     if (mListener != null) {
626                                         mListenerNeedsUpdate = false;
627                                         mListener.onFeedChanged(this, mListenerNeedsLayout);
628                                         mListenerNeedsLayout = false;
629                                     }
630                                     sleepMs = 100;
631                                     scanMediaSets = false;
632                                 }
633                                 if (!set.setContainsValidItems()) {
634                                     mediaSets.remove(set);
635                                     if (mListener != null) {
636                                         mListenerNeedsUpdate = false;
637                                         mListener.onFeedChanged(this, mListenerNeedsLayout);
638                                         mListenerNeedsLayout = false;
639                                     }
640                                     break;
641                                 }
642                             }
643                         }
644                         numSets = mediaSets.size();
645                         for (int i = 0; i < numSets; ++i) {
646                             MediaSet set = mediaSets.get(i);
647                             if (i >= bufferedRange.begin && i <= bufferedRange.end) {
648                                 if (scanMediaSets) {
649                                     int numItemsLoaded = set.mNumItemsLoaded;
650                                     if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
651                                         synchronized (set) {
652                                             dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
653                                             set.checkForDeletedItems();
654                                         }
655                                         if (set.getNumExpectedItems() == 0) {
656                                             mediaSets.remove(set);
657                                             break;
658                                         }
659                                         if (mListener != null) {
660                                             mListenerNeedsUpdate = false;
661                                             mListener.onFeedChanged(this, mListenerNeedsLayout);
662                                             mListenerNeedsLayout = false;
663                                         }
664                                         sleepMs = 100;
665                                         scanMediaSets = false;
666                                     }
667                                 }
668                             } else if (!mListenerNeedsUpdate && (i < bufferedRange.begin || i > bufferedRange.end)) {
669                                 // Purge this set to its initial status.
670                                 MediaClustering clustering = mClusterSets.get(set);
671                                 if (clustering != null) {
672                                     clustering.clear();
673                                     mClusterSets.remove(set);
674                                 }
675                                 if (set.getNumItems() != 0)
676                                     set.clear();
677                             }
678                         }
679                     }
680                     if (expandedSetIndex != Shared.INVALID) {
681                         int numSets = mMediaSets.size();
682                         for (int i = 0; i < numSets; ++i) {
683                             // Purge other sets.
684                             if (i != expandedSetIndex) {
685                                 MediaSet set = mediaSets.get(i);
686                                 MediaClustering clustering = mClusterSets.get(set);
687                                 if (clustering != null) {
688                                     clustering.clear();
689                                     mClusterSets.remove(set);
690                                 }
691                                 if (set.mNumItemsLoaded != 0)
692                                     set.clear();
693                             }
694                         }
695                         // Make sure all the items are loaded for the album.
696                         int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded;
697                         int requestedItems = mVisibleRange.end;
698                         // requestedItems count changes in clustering mode.
699                         if (mInClusteringMode && mClusterSets != null) {
700                             requestedItems = 0;
701                             MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex));
702                             if (clustering != null) {
703                                 ArrayList<Cluster> clusters = clustering.getClustersForDisplay();
704                                 int numClusters = clusters.size();
705                                 for (int i = 0; i < numClusters; i++) {
706                                     requestedItems += clusters.get(i).getNumExpectedItems();
707                                 }
708                             }
709                         }
710                         MediaSet set = mediaSets.get(expandedSetIndex);
711                         if (numItemsLoaded < set.getNumExpectedItems()) {
712                             // We perform calculations for a window that gets
713                             // anchored to a multiple of NUM_ITEMS_LOOKAHEAD.
714                             // The start of the window is 0, x, 2x, 3x ... etc
715                             // where x = NUM_ITEMS_LOOKAHEAD.
716                             synchronized (set) {
717                                 dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
718                                         * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
719                                 set.checkForDeletedItems();
720                             }
721                             if (set.getNumExpectedItems() == 0) {
722                                 mediaSets.remove(set);
723                                 mListenerNeedsUpdate = false;
724                                 mListener.onFeedChanged(this, mListenerNeedsLayout);
725                                 mListenerNeedsLayout = false;
726                             }
727                             if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
728                                 mListenerNeedsUpdate = false;
729                                 mListener.onFeedChanged(this, mListenerNeedsLayout);
730                                 mListenerNeedsLayout = false;
731                             }
732                         }
733                     }
734                     MediaFilter filter = mMediaFilter;
735                     if (filter != null && mMediaFilteredSet == null) {
736                         if (expandedSetIndex != Shared.INVALID) {
737                             MediaSet set = mediaSets.get(expandedSetIndex);
738                             ArrayList<MediaItem> items = set.getItems();
739                             int numItems = set.getNumItems();
740                             MediaSet filteredSet = new MediaSet();
741                             filteredSet.setNumExpectedItems(numItems);
742                             mMediaFilteredSet = filteredSet;
743                             for (int i = 0; i < numItems; ++i) {
744                                 MediaItem item = items.get(i);
745                                 if (filter.pass(item)) {
746                                     filteredSet.addItem(item);
747                                 }
748                             }
749                             filteredSet.updateNumExpectedItems();
750                             filteredSet.generateTitle(true);
751                         }
752                         updateListener(true);
753                     }
754                 }
755             }
756         }
757     }
758 
expandMediaSet(int mediaSetIndex)759     public void expandMediaSet(int mediaSetIndex) {
760         // We need to check if this slot can be focused or not.
761         if (mListener != null) {
762             mListener.onFeedAboutToChange(this);
763         }
764         if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) {
765             // We are collapsing a previously expanded media set
766             if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) {
767                 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex);
768                 if (set.getNumItems() == 0) {
769                     set.clear();
770                 }
771             }
772         }
773         mExpandedMediaSetIndex = mediaSetIndex;
774         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
775             // Notify Picasa that the user entered the album.
776             // MediaSet set = mMediaSets.get(mediaSetIndex);
777             // PicasaService.requestSync(mContext,
778             // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId);
779         }
780         updateListener(true);
781         mMediaFeedNeedsToRun = true;
782     }
783 
canExpandSet(int slotIndex)784     public boolean canExpandSet(int slotIndex) {
785         int mediaSetIndex = slotIndex;
786         if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) {
787             MediaSet set = mMediaSets.get(mediaSetIndex);
788             if (set.getNumItems() > 0) {
789                 MediaItem item = set.getItems().get(0);
790                 if (item.mId == Shared.INVALID) {
791                     return false;
792                 }
793                 return true;
794             }
795         }
796         return false;
797     }
798 
hasExpandedMediaSet()799     public boolean hasExpandedMediaSet() {
800         return (mExpandedMediaSetIndex != Shared.INVALID);
801     }
802 
restorePreviousClusteringState()803     public boolean restorePreviousClusteringState() {
804         boolean retVal = disableClusteringIfNecessary();
805         if (retVal) {
806             if (mListener != null) {
807                 mListener.onFeedAboutToChange(this);
808             }
809             updateListener(true);
810             mMediaFeedNeedsToRun = true;
811         }
812         return retVal;
813     }
814 
disableClusteringIfNecessary()815     private boolean disableClusteringIfNecessary() {
816         if (mInClusteringMode) {
817             // Disable clustering.
818             mInClusteringMode = false;
819             mMediaFeedNeedsToRun = true;
820             return true;
821         }
822         return false;
823     }
824 
isClustered()825     public boolean isClustered() {
826         return mInClusteringMode;
827     }
828 
getCurrentSet()829     public MediaSet getCurrentSet() {
830         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
831             return mMediaSets.get(mExpandedMediaSetIndex);
832         }
833         return null;
834     }
835 
performClustering()836     public void performClustering() {
837         if (mListener != null) {
838             mListener.onFeedAboutToChange(this);
839         }
840         MediaSet setToUse = null;
841         if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
842             setToUse = mMediaSets.get(mExpandedMediaSetIndex);
843         }
844         if (setToUse != null) {
845             MediaClustering clustering = null;
846             synchronized (mClusterSets) {
847                 // Make sure the computation is completed to the end.
848                 clustering = mClusterSets.get(setToUse);
849                 if (clustering != null) {
850                     clustering.compute(null, true);
851                 } else {
852                     return;
853                 }
854             }
855             mInClusteringMode = true;
856             updateListener(true);
857         }
858     }
859 
moveSetToFront(MediaSet mediaSet)860     public void moveSetToFront(MediaSet mediaSet) {
861         ArrayList<MediaSet> mediaSets = mMediaSets;
862         int numSets = mediaSets.size();
863         if (numSets == 0) {
864             mediaSets.add(mediaSet);
865             return;
866         }
867         MediaSet setToFind = mediaSets.get(0);
868         if (setToFind == mediaSet) {
869             return;
870         }
871         mediaSets.set(0, mediaSet);
872         int indexToSwapTill = -1;
873         for (int i = 1; i < numSets; ++i) {
874             MediaSet set = mediaSets.get(i);
875             if (set == mediaSet) {
876                 mediaSets.set(i, setToFind);
877                 indexToSwapTill = i;
878                 break;
879             }
880         }
881         if (indexToSwapTill != Shared.INVALID) {
882             for (int i = indexToSwapTill; i > 1; --i) {
883                 MediaSet setEnd = mediaSets.get(i);
884                 MediaSet setPrev = mediaSets.get(i - 1);
885                 mediaSets.set(i, setPrev);
886                 mediaSets.set(i - 1, setEnd);
887             }
888         }
889         mMediaFeedNeedsToRun = true;
890     }
891 
replaceMediaSet(long setId, DataSource dataSource)892     public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
893         Log.i(TAG, "Replacing media set " + setId);
894         final MediaSet set = getMediaSet(setId);
895         if (set != null)
896             set.refresh();
897         return set;
898     }
899 
setSingleImageMode(boolean singleImageMode)900     public void setSingleImageMode(boolean singleImageMode) {
901         mSingleImageMode = singleImageMode;
902     }
903 
isSingleImageMode()904     public boolean isSingleImageMode() {
905         return mSingleImageMode;
906     }
907 
getExpandedMediaSet()908     public MediaSet getExpandedMediaSet() {
909         if (mExpandedMediaSetIndex == Shared.INVALID)
910             return null;
911         if (mExpandedMediaSetIndex >= mMediaSets.size())
912             return null;
913         return mMediaSets.get(mExpandedMediaSetIndex);
914     }
915 
refresh()916     public void refresh() {
917         if (mDataSource != null) {
918             synchronized (mRequestedRefresh) {
919                 mRequestedRefresh.add(mDataSource.getDatabaseUris());
920             }
921         }
922     }
923 
refresh(final String[] databaseUris)924     private void refresh(final String[] databaseUris) {
925         synchronized (mMediaSets) {
926             if (mDataSource != null) {
927                 synchronized (mRequestedRefresh) {
928                     mRequestedRefresh.add(databaseUris);
929                 }
930             }
931         }
932     }
933 
onPause()934     public void onPause() {
935         final HashMap<String, ContentObserver> observers = mContentObservers;
936         final int numObservers = observers.size();
937         if (numObservers > 0) {
938             String[] uris = new String[numObservers];
939             final Set<String> keySet = observers.keySet();
940             if (keySet != null) {
941                 uris = keySet.toArray(uris);
942                 final int numUris = uris.length;
943                 final ContentResolver cr = mContext.getContentResolver();
944                 for (int i = 0; i < numUris; ++i) {
945                     final String uri = uris[i];
946                     if (uri != null) {
947                         final ContentObserver observer = observers.get(uri);
948                         cr.unregisterContentObserver(observer);
949                         observers.remove(uri);
950                     }
951                 }
952             }
953         }
954         observers.clear();
955     }
956 
onResume()957     public void onResume() {
958         final Context context = mContext;
959         final DataSource dataSource = mDataSource;
960         if (context == null || dataSource == null)
961             return;
962         // We setup the listeners for this datasource
963         final String[] uris = dataSource.getDatabaseUris();
964         final HashMap<String, ContentObserver> observers = mContentObservers;
965         if (context instanceof Gallery) {
966             final Gallery gallery = (Gallery) context;
967             final ContentResolver cr = context.getContentResolver();
968             if (uris != null) {
969                 final int numUris = uris.length;
970                 for (int i = 0; i < numUris; ++i) {
971                     final String uri = uris[i];
972                     final ContentObserver presentObserver = observers.get(uri);
973                     if (presentObserver == null) {
974                         final Handler handler = App.get(context).getHandler();
975                         final ContentObserver observer = new ContentObserver(handler) {
976                             public void onChange(boolean selfChange) {
977                                 if (!mWaitingForMediaScanner) {
978                                     MediaFeed.this.refresh(new String[] { uri });
979                                 }
980                             }
981                         };
982                         cr.registerContentObserver(Uri.parse(uri), true, observer);
983                         observers.put(uri, observer);
984                     }
985                 }
986             }
987         }
988         refresh();
989     }
990 }
991