2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.cooliris.media;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
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;
34 import com.cooliris.app.App;
35 import com.cooliris.app.Res;
36 import com.cooliris.media.MediaClustering.Cluster;
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;
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;
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;
72 public interface Listener {
73 public abstract void onFeedAboutToChange(MediaFeed feed);
75 public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout);
78 public MediaFeed(Context context, DataSource dataSource, Listener listener) {
81 mDataSource = dataSource;
82 mSingleWrapper.setNumExpectedItems(1);
86 public void shutdown() {
88 if (mDataSourceThread != null) {
89 mDataSource.shutdown();
90 repeatShuttingDownThread(mDataSourceThread);
91 mDataSourceThread = null;
93 if (mAlbumSourceThread != null) {
94 repeatShuttingDownThread(mAlbumSourceThread);
95 mAlbumSourceThread = null;
97 int numSets = mMediaSets.size();
98 for (int i = 0; i < numSets; ++i) {
99 MediaSet set = mMediaSets.get(i);
102 synchronized (mMediaSets) {
105 int numClusters = mClusterSets.size();
106 for (int i = 0; i < numClusters; ++i) {
107 MediaClustering mc = mClusterSets.get(i);
112 mClusterSets.clear();
115 mSingleWrapper = null;
118 private void repeatShuttingDownThread(Thread targetThread) {
119 for (int i = 0; i < NUM_INTERRUPT_RETRIES && targetThread.isAlive(); ++i) {
120 targetThread.interrupt();
122 targetThread.join(JOIN_TIMEOUT);
123 } catch (InterruptedException e) {
124 Log.w(TAG, "Cannot stop the thread: " + targetThread.getName(), e);
125 Thread.currentThread().interrupt();
130 if (targetThread.isAlive()) {
131 Log.w(TAG, "Cannot stop the thread: " + targetThread.getName());
135 public void setVisibleRange(int begin, int end) {
136 if (begin != mVisibleRange.begin || end != mVisibleRange.end) {
137 mVisibleRange.begin = begin;
138 mVisibleRange.end = end;
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;
148 public void setFilter(MediaFilter filter) {
149 mMediaFilter = filter;
150 mMediaFilteredSet = null;
151 if (mListener != null) {
152 mListener.onFeedAboutToChange(this);
154 mMediaFeedNeedsToRun = true;
157 public void removeFilter() {
159 mMediaFilteredSet = null;
160 if (mListener != null) {
161 mListener.onFeedAboutToChange(this);
162 updateListener(true);
164 mMediaFeedNeedsToRun = true;
167 public ArrayList<MediaSet> getMediaSets() {
171 public MediaSet getMediaSet(final long setId) {
172 if (setId != Shared.INVALID) {
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;
182 } catch (Exception e) {
189 public MediaSet getFilteredSet() {
190 return mMediaFilteredSet;
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();
200 mMediaFeedNeedsToRun = true;
204 public DataSource getDataSource() {
208 public MediaClustering getClustering() {
209 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
210 return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex));
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();
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);
236 clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems());
237 clustering.addItemForClustering(item);
238 item.mClusteringState = MediaItem.CLUSTERED;
241 mMediaFeedNeedsToRun = true;
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));
250 if (operation == OPERATION_DELETE && mListener != null) {
251 mListener.onFeedAboutToChange(this);
253 Thread operationThread = new Thread(new Runnable() {
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.
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
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);
278 set.updateNumExpectedItems();
279 set.generateTitle(true);
282 updateListener(true);
283 mMediaFeedNeedsToRun = true;
284 if (mDataSource != null) {
285 mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null);
288 mDataSource.performOperation(operation, mediaBuckets, data);
292 operationThread.setName("Operation " + operation);
293 operationThread.start();
296 public void removeMediaSet(MediaSet set) {
297 synchronized (mMediaSets) {
298 mMediaSets.remove(set);
300 mMediaFeedNeedsToRun = true;
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);
311 mMediaFeedNeedsToRun = true;
314 public void updateListener(boolean needsLayout) {
315 mListenerNeedsUpdate = true;
316 mListenerNeedsLayout = needsLayout;
319 public int getNumSlots() {
320 int currentMediaSetIndex = mExpandedMediaSetIndex;
321 ArrayList<MediaSet> mediaSets = mMediaSets;
322 int mediaSetsSize = mediaSets.size();
324 if (mInClusteringMode == false) {
325 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
326 return mediaSetsSize;
328 MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
329 return setToUse.getNumExpectedItems();
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();
341 public void copySlotStateFrom(MediaFeed another) {
342 mExpandedMediaSetIndex = another.mExpandedMediaSetIndex;
343 mInClusteringMode = another.mInClusteringMode;
346 public ArrayList<Integer> getBreaks() {
349 int currentMediaSetIndex = mExpandedMediaSetIndex;
350 ArrayList<MediaSet> mediaSets = mMediaSets;
351 int mediaSetsSize = mediaSets.size();
352 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize)
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);
362 for (int i = 0; i < numClusters; ++i) {
363 size += clusters.get(i).getItems().size();
372 public MediaSet getSetForSlot(int slotIndex) {
377 ArrayList<MediaSet> mediaSets = mMediaSets;
378 int mediaSetsSize = mediaSets.size();
379 int currentMediaSetIndex = mExpandedMediaSetIndex;
381 if (mInClusteringMode == false) {
382 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) {
383 if (slotIndex >= mediaSetsSize) {
386 return mMediaSets.get(slotIndex);
388 if (mSingleWrapper.getNumItems() == 0) {
389 mSingleWrapper.addItem(null);
391 MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet;
392 ArrayList<MediaItem> items = setToUse.getItems();
393 if (slotIndex >= setToUse.getNumItems()) {
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);
413 public boolean getWaitingForMediaScanner() {
414 return mWaitingForMediaScanner;
417 public boolean isLoading() {
421 public void start() {
422 final MediaFeed feed = this;
425 mDataSourceThread = new Thread(this);
426 mDataSourceThread.setName("MediaFeed");
428 mAlbumSourceThread = new Thread(new Runnable() {
430 if (mContext == null)
432 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
433 DataSource dataSource = mDataSource;
434 // We must wait while the SD card is mounted or the MediaScanner
436 if (dataSource != null) {
439 mWaitingForMediaScanner = false;
440 while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) {
441 // MediaScanner is still running, wait
442 if (Thread.interrupted())
444 mWaitingForMediaScanner = true;
446 if (mContext == null)
448 showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG);
449 if (dataSource != null) {
453 } catch (InterruptedException e) {
457 if (mWaitingForMediaScanner) {
458 showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG);
459 mWaitingForMediaScanner = false;
465 mAlbumSourceThread.setName("MediaSets");
466 mAlbumSourceThread.start();
469 private void loadMediaSets() {
470 if (mDataSource == null)
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;
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);
488 int numSetsToRemove = setsToRemove.size();
489 for (int i = 0; i < numSetsToRemove; ++i) {
490 sets.remove(setsToRemove.get(i));
492 setsToRemove.clear();
494 mMediaFeedNeedsToRun = true;
495 updateListener(false);
498 private void showToast(final String string, final int duration) {
499 showToast(string, duration, false);
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() {
506 if (mContext != null) {
507 Toast toast = Toast.makeText(mContext, string, duration);
509 toast.setGravity(Gravity.CENTER, 0, 0);
519 DataSource dataSource = mDataSource;
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));
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];
545 databaseUris = new String[0];
546 databaseUris = (String[]) uris.keySet().toArray(databaseUris);
549 boolean settingFeedAboutToChange = false;
550 if (performRefresh) {
551 if (dataSource != null) {
552 if (mListener != null) {
553 settingFeedAboutToChange = true;
554 mListener.onFeedAboutToChange(this);
556 dataSource.refresh(this, databaseUris);
557 mMediaFeedNeedsToRun = true;
560 if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) {
561 mListenerNeedsUpdate = false;
562 if (mListener != null)
563 synchronized (mMediaSets) {
564 mListener.onFeedChanged(this, mListenerNeedsLayout);
567 Thread.sleep(sleepMs);
568 } catch (InterruptedException e) {
573 Thread.sleep(sleepMs);
574 } catch (InterruptedException e) {
579 if (!mMediaFeedNeedsToRun)
581 App app = App.get(mContext);
582 if (app == null || app.isPaused())
584 if (settingFeedAboutToChange) {
585 updateListener(true);
587 mMediaFeedNeedsToRun = false;
588 ArrayList<MediaSet> mediaSets = mMediaSets;
589 synchronized (mediaSets) {
590 int expandedSetIndex = mExpandedMediaSetIndex;
591 if (expandedSetIndex >= mMediaSets.size()) {
592 expandedSetIndex = Shared.INVALID;
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) {
606 dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
607 set.checkForDeletedItems();
609 if (set.getNumExpectedItems() == 0) {
610 mediaSets.remove(set);
613 if (mListener != null) {
614 mListenerNeedsUpdate = false;
615 mListener.onFeedChanged(this, mListenerNeedsLayout);
616 mListenerNeedsLayout = false;
619 scanMediaSets = false;
621 if (!set.setContainsValidItems()) {
622 mediaSets.remove(set);
623 if (mListener != null) {
624 mListenerNeedsUpdate = false;
625 mListener.onFeedChanged(this, mListenerNeedsLayout);
626 mListenerNeedsLayout = false;
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) {
637 int numItemsLoaded = set.mNumItemsLoaded;
638 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) {
640 dataSource.loadItemsForSet(this, set, numItemsLoaded, 8);
641 set.checkForDeletedItems();
643 if (set.getNumExpectedItems() == 0) {
644 mediaSets.remove(set);
647 if (mListener != null) {
648 mListenerNeedsUpdate = false;
649 mListener.onFeedChanged(this, mListenerNeedsLayout);
650 mListenerNeedsLayout = false;
653 scanMediaSets = false;
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) {
661 mClusterSets.remove(set);
663 if (set.getNumItems() != 0)
668 if (expandedSetIndex != Shared.INVALID) {
669 int numSets = mMediaSets.size();
670 for (int i = 0; i < numSets; ++i) {
672 if (i != expandedSetIndex) {
673 MediaSet set = mediaSets.get(i);
674 MediaClustering clustering = mClusterSets.get(set);
675 if (clustering != null) {
677 mClusterSets.remove(set);
679 if (set.mNumItemsLoaded != 0)
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) {
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();
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.
705 dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD)
706 * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD);
707 set.checkForDeletedItems();
709 if (set.getNumExpectedItems() == 0) {
710 mediaSets.remove(set);
711 mListenerNeedsUpdate = false;
712 mListener.onFeedChanged(this, mListenerNeedsLayout);
713 mListenerNeedsLayout = false;
715 if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) {
716 mListenerNeedsUpdate = false;
717 mListener.onFeedChanged(this, mListenerNeedsLayout);
718 mListenerNeedsLayout = false;
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);
737 filteredSet.updateNumExpectedItems();
738 filteredSet.generateTitle(true);
740 updateListener(true);
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);
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) {
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);
768 updateListener(true);
769 mMediaFeedNeedsToRun = true;
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) {
787 public boolean hasExpandedMediaSet() {
788 return (mExpandedMediaSetIndex != Shared.INVALID);
791 public boolean restorePreviousClusteringState() {
792 boolean retVal = disableClusteringIfNecessary();
794 if (mListener != null) {
795 mListener.onFeedAboutToChange(this);
797 updateListener(true);
798 mMediaFeedNeedsToRun = true;
803 private boolean disableClusteringIfNecessary() {
804 if (mInClusteringMode) {
805 // Disable clustering.
806 mInClusteringMode = false;
807 mMediaFeedNeedsToRun = true;
813 public boolean isClustered() {
814 return mInClusteringMode;
817 public MediaSet getCurrentSet() {
818 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
819 return mMediaSets.get(mExpandedMediaSetIndex);
824 public void performClustering() {
825 if (mListener != null) {
826 mListener.onFeedAboutToChange(this);
828 MediaSet setToUse = null;
829 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) {
830 setToUse = mMediaSets.get(mExpandedMediaSetIndex);
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);
843 mInClusteringMode = true;
844 updateListener(true);
848 public void moveSetToFront(MediaSet mediaSet) {
849 ArrayList<MediaSet> mediaSets = mMediaSets;
850 int numSets = mediaSets.size();
852 mediaSets.add(mediaSet);
855 MediaSet setToFind = mediaSets.get(0);
856 if (setToFind == mediaSet) {
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);
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);
877 mMediaFeedNeedsToRun = true;
880 public MediaSet replaceMediaSet(long setId, DataSource dataSource) {
881 Log.i(TAG, "Replacing media set " + setId);
882 final MediaSet set = getMediaSet(setId);
888 public void setSingleImageMode(boolean singleImageMode) {
889 mSingleImageMode = singleImageMode;
892 public boolean isSingleImageMode() {
893 return mSingleImageMode;
896 public MediaSet getExpandedMediaSet() {
897 if (mExpandedMediaSetIndex == Shared.INVALID)
899 if (mExpandedMediaSetIndex >= mMediaSets.size())
901 return mMediaSets.get(mExpandedMediaSetIndex);
904 public void refresh() {
905 if (mDataSource != null) {
906 synchronized (mRequestedRefresh) {
907 mRequestedRefresh.add(mDataSource.getDatabaseUris());
912 private void refresh(final String[] databaseUris) {
913 synchronized (mMediaSets) {
914 if (mDataSource != null) {
915 synchronized (mRequestedRefresh) {
916 mRequestedRefresh.add(databaseUris);
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];
935 final ContentObserver observer = observers.get(uri);
936 cr.unregisterContentObserver(observer);
937 observers.remove(uri);
945 public void onResume() {
946 final Context context = mContext;
947 final DataSource dataSource = mDataSource;
948 if (context == null || dataSource == null)
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();
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 });
970 cr.registerContentObserver(Uri.parse(uri), true, observer);
971 observers.put(uri, observer);