Home | History | Annotate | Download | only in media
      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 }
    979