| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.cooliris.media; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.database.ContentObserver; |
| import android.util.Log; |
| import android.view.Gravity; |
| import android.widget.Toast; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.Process; |
| |
| import com.cooliris.app.App; |
| import com.cooliris.app.Res; |
| import com.cooliris.media.MediaClustering.Cluster; |
| |
| public final class MediaFeed implements Runnable { |
| private final String TAG = "MediaFeed"; |
| public static final int OPERATION_DELETE = 0; |
| public static final int OPERATION_ROTATE = 1; |
| public static final int OPERATION_CROP = 2; |
| |
| private static final int NUM_ITEMS_LOOKAHEAD = 60; |
| private static final int NUM_INTERRUPT_RETRIES = 30; |
| private static final int JOIN_TIMEOUT = 50; |
| |
| private IndexRange mVisibleRange = new IndexRange(); |
| private IndexRange mBufferedRange = new IndexRange(); |
| private ArrayList<MediaSet> mMediaSets = new ArrayList<MediaSet>(); |
| private Listener mListener; |
| private DataSource mDataSource; |
| private boolean mListenerNeedsUpdate = false; |
| private boolean mMediaFeedNeedsToRun = false; |
| private MediaSet mSingleWrapper = new MediaSet(); |
| private boolean mInClusteringMode = false; |
| private HashMap<MediaSet, MediaClustering> mClusterSets = new HashMap<MediaSet, MediaClustering>(32); |
| private int mExpandedMediaSetIndex = Shared.INVALID; |
| private MediaFilter mMediaFilter; |
| private MediaSet mMediaFilteredSet; |
| private Context mContext; |
| private Thread mDataSourceThread = null; |
| private Thread mAlbumSourceThread = null; |
| private boolean mListenerNeedsLayout; |
| private boolean mWaitingForMediaScanner; |
| private boolean mSingleImageMode; |
| private boolean mLoading; |
| private HashMap<String, ContentObserver> mContentObservers = new HashMap<String, ContentObserver>(); |
| private ArrayList<String[]> mRequestedRefresh = new ArrayList<String[]>(); |
| private volatile boolean mIsShutdown = false; |
| |
| public interface Listener { |
| public abstract void onFeedAboutToChange(MediaFeed feed); |
| |
| public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout); |
| } |
| |
| public MediaFeed(Context context, DataSource dataSource, Listener listener) { |
| mContext = context; |
| mListener = listener; |
| mDataSource = dataSource; |
| mSingleWrapper.setNumExpectedItems(1); |
| mLoading = true; |
| } |
| |
| public void shutdown() { |
| mIsShutdown = true; |
| if (mDataSourceThread != null) { |
| mDataSource.shutdown(); |
| repeatShuttingDownThread(mDataSourceThread); |
| mDataSourceThread = null; |
| } |
| if (mAlbumSourceThread != null) { |
| repeatShuttingDownThread(mAlbumSourceThread); |
| mAlbumSourceThread = null; |
| } |
| int numSets = mMediaSets.size(); |
| for (int i = 0; i < numSets; ++i) { |
| MediaSet set = mMediaSets.get(i); |
| set.clear(); |
| } |
| synchronized (mMediaSets) { |
| mMediaSets.clear(); |
| } |
| int numClusters = mClusterSets.size(); |
| for (int i = 0; i < numClusters; ++i) { |
| MediaClustering mc = mClusterSets.get(i); |
| if (mc != null) { |
| mc.clear(); |
| } |
| } |
| mClusterSets.clear(); |
| mListener = null; |
| mDataSource = null; |
| mSingleWrapper = null; |
| } |
| |
| private void repeatShuttingDownThread(Thread targetThread) { |
| for (int i = 0; i < NUM_INTERRUPT_RETRIES && targetThread.isAlive(); ++i) { |
| targetThread.interrupt(); |
| try { |
| targetThread.join(JOIN_TIMEOUT); |
| } catch (InterruptedException e) { |
| Log.w(TAG, "Cannot stop the thread: " + targetThread.getName(), e); |
| Thread.currentThread().interrupt(); |
| return; |
| } |
| } |
| |
| if (targetThread.isAlive()) { |
| Log.w(TAG, "Cannot stop the thread: " + targetThread.getName()); |
| } |
| } |
| |
| public void setVisibleRange(int begin, int end) { |
| if (begin != mVisibleRange.begin || end != mVisibleRange.end) { |
| mVisibleRange.begin = begin; |
| mVisibleRange.end = end; |
| int numItems = 96; |
| int numItemsBy2 = numItems / 2; |
| int numItemsBy4 = numItems / 4; |
| mBufferedRange.begin = (begin / numItemsBy2) * numItemsBy2 - numItemsBy4; |
| mBufferedRange.end = mBufferedRange.begin + numItems; |
| mMediaFeedNeedsToRun = true; |
| } |
| } |
| |
| public void setFilter(MediaFilter filter) { |
| mMediaFilter = filter; |
| mMediaFilteredSet = null; |
| if (mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public void removeFilter() { |
| mMediaFilter = null; |
| mMediaFilteredSet = null; |
| if (mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| updateListener(true); |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public ArrayList<MediaSet> getMediaSets() { |
| return mMediaSets; |
| } |
| |
| public MediaSet getMediaSet(final long setId) { |
| if (setId != Shared.INVALID) { |
| try { |
| int mMediaSetsSize = mMediaSets.size(); |
| for (int i = 0; i < mMediaSetsSize; i++) { |
| final MediaSet set = mMediaSets.get(i); |
| if (set.mId == setId) { |
| set.mFlagForDelete = false; |
| return set; |
| } |
| } |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| public MediaSet getFilteredSet() { |
| return mMediaFilteredSet; |
| } |
| |
| public MediaSet addMediaSet(final long setId, DataSource dataSource) { |
| MediaSet mediaSet = new MediaSet(dataSource); |
| mediaSet.mId = setId; |
| mMediaSets.add(mediaSet); |
| if (mDataSourceThread != null && !mDataSourceThread.isAlive()) { |
| mDataSourceThread.start(); |
| } |
| mMediaFeedNeedsToRun = true; |
| return mediaSet; |
| } |
| |
| public DataSource getDataSource() { |
| return mDataSource; |
| } |
| |
| public MediaClustering getClustering() { |
| if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { |
| return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex)); |
| } |
| return null; |
| } |
| |
| public ArrayList<Cluster> getClustersForSet(final MediaSet set) { |
| ArrayList<Cluster> clusters = null; |
| if (mClusterSets != null && mClusterSets.containsKey(set)) { |
| MediaClustering mediaClustering = mClusterSets.get(set); |
| if (mediaClustering != null) { |
| clusters = mediaClustering.getClusters(); |
| } |
| } |
| return clusters; |
| } |
| |
| public void addItemToMediaSet(MediaItem item, MediaSet mediaSet) { |
| item.mParentMediaSet = mediaSet; |
| mediaSet.addItem(item); |
| synchronized (mClusterSets) { |
| if (item.mClusteringState == MediaItem.NOT_CLUSTERED) { |
| MediaClustering clustering = mClusterSets.get(mediaSet); |
| if (clustering == null) { |
| clustering = new MediaClustering(mediaSet.isPicassaAlbum()); |
| mClusterSets.put(mediaSet, clustering); |
| } |
| clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems()); |
| clustering.addItemForClustering(item); |
| item.mClusteringState = MediaItem.CLUSTERED; |
| } |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public void performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) { |
| int numBuckets = mediaBuckets.size(); |
| final ArrayList<MediaBucket> copyMediaBuckets = new ArrayList<MediaBucket>(numBuckets); |
| for (int i = 0; i < numBuckets; ++i) { |
| copyMediaBuckets.add(mediaBuckets.get(i)); |
| } |
| if (operation == OPERATION_DELETE && mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| } |
| Thread operationThread = new Thread(new Runnable() { |
| public void run() { |
| ArrayList<MediaBucket> mediaBuckets = copyMediaBuckets; |
| if (operation == OPERATION_DELETE) { |
| int numBuckets = mediaBuckets.size(); |
| for (int i = 0; i < numBuckets; ++i) { |
| MediaBucket bucket = mediaBuckets.get(i); |
| MediaSet set = bucket.mediaSet; |
| ArrayList<MediaItem> items = bucket.mediaItems; |
| if (set != null && items == null) { |
| // Remove the entire bucket. |
| removeMediaSet(set); |
| } else if (set != null && items != null) { |
| // We need to remove these items from the set. |
| int numItems = items.size(); |
| // We also need to delete the items from the |
| // cluster. |
| MediaClustering clustering = mClusterSets.get(set); |
| for (int j = 0; j < numItems; ++j) { |
| MediaItem item = items.get(j); |
| removeItemFromMediaSet(item, set); |
| if (clustering != null) { |
| clustering.removeItemFromClustering(item); |
| } |
| } |
| set.updateNumExpectedItems(); |
| set.generateTitle(true); |
| } |
| } |
| updateListener(true); |
| mMediaFeedNeedsToRun = true; |
| if (mDataSource != null) { |
| mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null); |
| } |
| } else { |
| mDataSource.performOperation(operation, mediaBuckets, data); |
| } |
| } |
| }); |
| operationThread.setName("Operation " + operation); |
| operationThread.start(); |
| } |
| |
| public void removeMediaSet(MediaSet set) { |
| synchronized (mMediaSets) { |
| mMediaSets.remove(set); |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| private void removeItemFromMediaSet(MediaItem item, MediaSet mediaSet) { |
| mediaSet.removeItem(item); |
| synchronized (mClusterSets) { |
| MediaClustering clustering = mClusterSets.get(mediaSet); |
| if (clustering != null) { |
| clustering.removeItemFromClustering(item); |
| } |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public void updateListener(boolean needsLayout) { |
| mListenerNeedsUpdate = true; |
| mListenerNeedsLayout = needsLayout; |
| } |
| |
| public int getNumSlots() { |
| int currentMediaSetIndex = mExpandedMediaSetIndex; |
| ArrayList<MediaSet> mediaSets = mMediaSets; |
| int mediaSetsSize = mediaSets.size(); |
| |
| if (mInClusteringMode == false) { |
| if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) { |
| return mediaSetsSize; |
| } else { |
| MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet; |
| return setToUse.getNumExpectedItems(); |
| } |
| } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) { |
| MediaSet set = mediaSets.get(currentMediaSetIndex); |
| MediaClustering clustering = mClusterSets.get(set); |
| if (clustering != null) { |
| return clustering.getClustersForDisplay().size(); |
| } |
| } |
| return 0; |
| } |
| |
| public void copySlotStateFrom(MediaFeed another) { |
| mExpandedMediaSetIndex = another.mExpandedMediaSetIndex; |
| mInClusteringMode = another.mInClusteringMode; |
| } |
| |
| public ArrayList<Integer> getBreaks() { |
| if (true) |
| return null; |
| int currentMediaSetIndex = mExpandedMediaSetIndex; |
| ArrayList<MediaSet> mediaSets = mMediaSets; |
| int mediaSetsSize = mediaSets.size(); |
| if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) |
| return null; |
| MediaSet set = mediaSets.get(currentMediaSetIndex); |
| MediaClustering clustering = mClusterSets.get(set); |
| if (clustering != null) { |
| clustering.compute(null, true); |
| final ArrayList<Cluster> clusters = clustering.getClustersForDisplay(); |
| int numClusters = clusters.size(); |
| final ArrayList<Integer> retVal = new ArrayList<Integer>(numClusters); |
| int size = 0; |
| for (int i = 0; i < numClusters; ++i) { |
| size += clusters.get(i).getItems().size(); |
| retVal.add(size); |
| } |
| return retVal; |
| } else { |
| return null; |
| } |
| } |
| |
| public MediaSet getSetForSlot(int slotIndex) { |
| if (slotIndex < 0) { |
| return null; |
| } |
| |
| ArrayList<MediaSet> mediaSets = mMediaSets; |
| int mediaSetsSize = mediaSets.size(); |
| int currentMediaSetIndex = mExpandedMediaSetIndex; |
| |
| if (mInClusteringMode == false) { |
| if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) { |
| if (slotIndex >= mediaSetsSize) { |
| return null; |
| } |
| return mMediaSets.get(slotIndex); |
| } |
| if (mSingleWrapper.getNumItems() == 0) { |
| mSingleWrapper.addItem(null); |
| } |
| MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet; |
| ArrayList<MediaItem> items = setToUse.getItems(); |
| if (slotIndex >= setToUse.getNumItems()) { |
| return null; |
| } |
| mSingleWrapper.getItems().set(0, items.get(slotIndex)); |
| return mSingleWrapper; |
| } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) { |
| MediaSet set = mediaSets.get(currentMediaSetIndex); |
| MediaClustering clustering = mClusterSets.get(set); |
| if (clustering != null) { |
| ArrayList<MediaClustering.Cluster> clusters = clustering.getClustersForDisplay(); |
| if (clusters.size() > slotIndex) { |
| MediaClustering.Cluster cluster = clusters.get(slotIndex); |
| cluster.generateCaption(mContext); |
| return cluster; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public boolean getWaitingForMediaScanner() { |
| return mWaitingForMediaScanner; |
| } |
| |
| public boolean isLoading() { |
| return mLoading; |
| } |
| |
| public void start() { |
| final MediaFeed feed = this; |
| onResume(); |
| mLoading = true; |
| mDataSourceThread = new Thread(this); |
| mDataSourceThread.setName("MediaFeed"); |
| mIsShutdown = false; |
| mAlbumSourceThread = new Thread(new Runnable() { |
| public void run() { |
| if (mContext == null) |
| return; |
| Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
| DataSource dataSource = mDataSource; |
| // We must wait while the SD card is mounted or the MediaScanner |
| // is running. |
| if (dataSource != null) { |
| loadMediaSets(); |
| } |
| mWaitingForMediaScanner = false; |
| while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) { |
| // MediaScanner is still running, wait |
| if (Thread.interrupted()) |
| return; |
| mWaitingForMediaScanner = true; |
| try { |
| if (mContext == null) |
| return; |
| showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG); |
| if (dataSource != null) { |
| loadMediaSets(); |
| } |
| Thread.sleep(10000); |
| } catch (InterruptedException e) { |
| return; |
| } |
| } |
| if (mWaitingForMediaScanner) { |
| showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG); |
| mWaitingForMediaScanner = false; |
| loadMediaSets(); |
| } |
| mLoading = false; |
| } |
| }); |
| mAlbumSourceThread.setName("MediaSets"); |
| mAlbumSourceThread.start(); |
| } |
| |
| private void loadMediaSets() { |
| if (mDataSource == null) |
| return; |
| final ArrayList<MediaSet> sets = mMediaSets; |
| synchronized (sets) { |
| final int numSets = sets.size(); |
| for (int i = 0; i < numSets; ++i) { |
| final MediaSet set = sets.get(i); |
| set.mFlagForDelete = true; |
| } |
| mDataSource.refresh(MediaFeed.this, mDataSource.getDatabaseUris()); |
| mDataSource.loadMediaSets(MediaFeed.this); |
| final ArrayList<MediaSet> setsToRemove = new ArrayList<MediaSet>(); |
| for (int i = 0; i < numSets; ++i) { |
| final MediaSet set = sets.get(i); |
| if (set.mFlagForDelete) { |
| setsToRemove.add(set); |
| } |
| } |
| int numSetsToRemove = setsToRemove.size(); |
| for (int i = 0; i < numSetsToRemove; ++i) { |
| sets.remove(setsToRemove.get(i)); |
| } |
| setsToRemove.clear(); |
| } |
| mMediaFeedNeedsToRun = true; |
| updateListener(false); |
| } |
| |
| private void showToast(final String string, final int duration) { |
| showToast(string, duration, false); |
| } |
| |
| private void showToast(final String string, final int duration, final boolean centered) { |
| if (mContext != null && !App.get(mContext).isPaused()) { |
| App.get(mContext).getHandler().post(new Runnable() { |
| public void run() { |
| if (mContext != null) { |
| Toast toast = Toast.makeText(mContext, string, duration); |
| if (centered) { |
| toast.setGravity(Gravity.CENTER, 0, 0); |
| } |
| toast.show(); |
| } |
| } |
| }); |
| } |
| } |
| |
| public void run() { |
| DataSource dataSource = mDataSource; |
| int sleepMs = 10; |
| Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
| if (dataSource != null) { |
| while (!Thread.interrupted() && !mIsShutdown) { |
| String[] databaseUris = null; |
| boolean performRefresh = false; |
| synchronized (mRequestedRefresh) { |
| if (mRequestedRefresh.size() > 0) { |
| // We prune this first. |
| int numRequests = mRequestedRefresh.size(); |
| for (int i = 0; i < numRequests; ++i) { |
| databaseUris = ArrayUtils.addAll(databaseUris, mRequestedRefresh.get(i)); |
| } |
| mRequestedRefresh.clear(); |
| performRefresh = true; |
| // We need to eliminate duplicate uris in this array |
| final HashMap<String, String> uris = new HashMap<String, String>(); |
| if (databaseUris != null) { |
| int numUris = databaseUris.length; |
| for (int i = 0; i < numUris; ++i) { |
| final String uri = databaseUris[i]; |
| if (uri != null) |
| uris.put(uri, uri); |
| } |
| } |
| databaseUris = new String[0]; |
| databaseUris = (String[]) uris.keySet().toArray(databaseUris); |
| } |
| } |
| boolean settingFeedAboutToChange = false; |
| if (performRefresh) { |
| if (dataSource != null) { |
| if (mListener != null) { |
| settingFeedAboutToChange = true; |
| mListener.onFeedAboutToChange(this); |
| } |
| dataSource.refresh(this, databaseUris); |
| mMediaFeedNeedsToRun = true; |
| } |
| } |
| if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) { |
| mListenerNeedsUpdate = false; |
| if (mListener != null) |
| synchronized (mMediaSets) { |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| } |
| try { |
| Thread.sleep(sleepMs); |
| } catch (InterruptedException e) { |
| return; |
| } |
| } else { |
| try { |
| Thread.sleep(sleepMs); |
| } catch (InterruptedException e) { |
| return; |
| } |
| } |
| sleepMs = 300; |
| if (!mMediaFeedNeedsToRun) |
| continue; |
| App app = App.get(mContext); |
| if (app == null || app.isPaused()) |
| continue; |
| if (settingFeedAboutToChange) { |
| updateListener(true); |
| } |
| mMediaFeedNeedsToRun = false; |
| ArrayList<MediaSet> mediaSets = mMediaSets; |
| synchronized (mediaSets) { |
| int expandedSetIndex = mExpandedMediaSetIndex; |
| if (expandedSetIndex >= mMediaSets.size()) { |
| expandedSetIndex = Shared.INVALID; |
| } |
| if (expandedSetIndex == Shared.INVALID) { |
| // We purge the sets outside this visibleRange. |
| int numSets = mediaSets.size(); |
| IndexRange visibleRange = mVisibleRange; |
| IndexRange bufferedRange = mBufferedRange; |
| boolean scanMediaSets = true; |
| for (int i = 0; i < numSets; ++i) { |
| if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) { |
| MediaSet set = mediaSets.get(i); |
| int numItemsLoaded = set.mNumItemsLoaded; |
| if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { |
| synchronized (set) { |
| dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); |
| set.checkForDeletedItems(); |
| } |
| if (set.getNumExpectedItems() == 0) { |
| mediaSets.remove(set); |
| break; |
| } |
| if (mListener != null) { |
| mListenerNeedsUpdate = false; |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| mListenerNeedsLayout = false; |
| } |
| sleepMs = 100; |
| scanMediaSets = false; |
| } |
| if (!set.setContainsValidItems()) { |
| mediaSets.remove(set); |
| if (mListener != null) { |
| mListenerNeedsUpdate = false; |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| mListenerNeedsLayout = false; |
| } |
| break; |
| } |
| } |
| } |
| numSets = mediaSets.size(); |
| for (int i = 0; i < numSets; ++i) { |
| MediaSet set = mediaSets.get(i); |
| if (i >= bufferedRange.begin && i <= bufferedRange.end) { |
| if (scanMediaSets) { |
| int numItemsLoaded = set.mNumItemsLoaded; |
| if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { |
| synchronized (set) { |
| dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); |
| set.checkForDeletedItems(); |
| } |
| if (set.getNumExpectedItems() == 0) { |
| mediaSets.remove(set); |
| break; |
| } |
| if (mListener != null) { |
| mListenerNeedsUpdate = false; |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| mListenerNeedsLayout = false; |
| } |
| sleepMs = 100; |
| scanMediaSets = false; |
| } |
| } |
| } else if (!mListenerNeedsUpdate && (i < bufferedRange.begin || i > bufferedRange.end)) { |
| // Purge this set to its initial status. |
| MediaClustering clustering = mClusterSets.get(set); |
| if (clustering != null) { |
| clustering.clear(); |
| mClusterSets.remove(set); |
| } |
| if (set.getNumItems() != 0) |
| set.clear(); |
| } |
| } |
| } |
| if (expandedSetIndex != Shared.INVALID) { |
| int numSets = mMediaSets.size(); |
| for (int i = 0; i < numSets; ++i) { |
| // Purge other sets. |
| if (i != expandedSetIndex) { |
| MediaSet set = mediaSets.get(i); |
| MediaClustering clustering = mClusterSets.get(set); |
| if (clustering != null) { |
| clustering.clear(); |
| mClusterSets.remove(set); |
| } |
| if (set.mNumItemsLoaded != 0) |
| set.clear(); |
| } |
| } |
| // Make sure all the items are loaded for the album. |
| int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded; |
| int requestedItems = mVisibleRange.end; |
| // requestedItems count changes in clustering mode. |
| if (mInClusteringMode && mClusterSets != null) { |
| requestedItems = 0; |
| MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex)); |
| if (clustering != null) { |
| ArrayList<Cluster> clusters = clustering.getClustersForDisplay(); |
| int numClusters = clusters.size(); |
| for (int i = 0; i < numClusters; i++) { |
| requestedItems += clusters.get(i).getNumExpectedItems(); |
| } |
| } |
| } |
| MediaSet set = mediaSets.get(expandedSetIndex); |
| if (numItemsLoaded < set.getNumExpectedItems()) { |
| // We perform calculations for a window that gets |
| // anchored to a multiple of NUM_ITEMS_LOOKAHEAD. |
| // The start of the window is 0, x, 2x, 3x ... etc |
| // where x = NUM_ITEMS_LOOKAHEAD. |
| synchronized (set) { |
| dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD) |
| * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD); |
| set.checkForDeletedItems(); |
| } |
| if (set.getNumExpectedItems() == 0) { |
| mediaSets.remove(set); |
| mListenerNeedsUpdate = false; |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| mListenerNeedsLayout = false; |
| } |
| if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) { |
| mListenerNeedsUpdate = false; |
| mListener.onFeedChanged(this, mListenerNeedsLayout); |
| mListenerNeedsLayout = false; |
| } |
| } |
| } |
| MediaFilter filter = mMediaFilter; |
| if (filter != null && mMediaFilteredSet == null) { |
| if (expandedSetIndex != Shared.INVALID) { |
| MediaSet set = mediaSets.get(expandedSetIndex); |
| ArrayList<MediaItem> items = set.getItems(); |
| int numItems = set.getNumItems(); |
| MediaSet filteredSet = new MediaSet(); |
| filteredSet.setNumExpectedItems(numItems); |
| mMediaFilteredSet = filteredSet; |
| for (int i = 0; i < numItems; ++i) { |
| MediaItem item = items.get(i); |
| if (filter.pass(item)) { |
| filteredSet.addItem(item); |
| } |
| } |
| filteredSet.updateNumExpectedItems(); |
| filteredSet.generateTitle(true); |
| } |
| updateListener(true); |
| } |
| } |
| } |
| } |
| } |
| |
| public void expandMediaSet(int mediaSetIndex) { |
| // We need to check if this slot can be focused or not. |
| if (mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| } |
| if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) { |
| // We are collapsing a previously expanded media set |
| if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) { |
| MediaSet set = mMediaSets.get(mExpandedMediaSetIndex); |
| if (set.getNumItems() == 0) { |
| set.clear(); |
| } |
| } |
| } |
| mExpandedMediaSetIndex = mediaSetIndex; |
| if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) { |
| // Notify Picasa that the user entered the album. |
| // MediaSet set = mMediaSets.get(mediaSetIndex); |
| // PicasaService.requestSync(mContext, |
| // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId); |
| } |
| updateListener(true); |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public boolean canExpandSet(int slotIndex) { |
| int mediaSetIndex = slotIndex; |
| if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) { |
| MediaSet set = mMediaSets.get(mediaSetIndex); |
| if (set.getNumItems() > 0) { |
| MediaItem item = set.getItems().get(0); |
| if (item.mId == Shared.INVALID) { |
| return false; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean hasExpandedMediaSet() { |
| return (mExpandedMediaSetIndex != Shared.INVALID); |
| } |
| |
| public boolean restorePreviousClusteringState() { |
| boolean retVal = disableClusteringIfNecessary(); |
| if (retVal) { |
| if (mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| } |
| updateListener(true); |
| mMediaFeedNeedsToRun = true; |
| } |
| return retVal; |
| } |
| |
| private boolean disableClusteringIfNecessary() { |
| if (mInClusteringMode) { |
| // Disable clustering. |
| mInClusteringMode = false; |
| mMediaFeedNeedsToRun = true; |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean isClustered() { |
| return mInClusteringMode; |
| } |
| |
| public MediaSet getCurrentSet() { |
| if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { |
| return mMediaSets.get(mExpandedMediaSetIndex); |
| } |
| return null; |
| } |
| |
| public void performClustering() { |
| if (mListener != null) { |
| mListener.onFeedAboutToChange(this); |
| } |
| MediaSet setToUse = null; |
| if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { |
| setToUse = mMediaSets.get(mExpandedMediaSetIndex); |
| } |
| if (setToUse != null) { |
| MediaClustering clustering = null; |
| synchronized (mClusterSets) { |
| // Make sure the computation is completed to the end. |
| clustering = mClusterSets.get(setToUse); |
| if (clustering != null) { |
| clustering.compute(null, true); |
| } else { |
| return; |
| } |
| } |
| mInClusteringMode = true; |
| updateListener(true); |
| } |
| } |
| |
| public void moveSetToFront(MediaSet mediaSet) { |
| ArrayList<MediaSet> mediaSets = mMediaSets; |
| int numSets = mediaSets.size(); |
| if (numSets == 0) { |
| mediaSets.add(mediaSet); |
| return; |
| } |
| MediaSet setToFind = mediaSets.get(0); |
| if (setToFind == mediaSet) { |
| return; |
| } |
| mediaSets.set(0, mediaSet); |
| int indexToSwapTill = -1; |
| for (int i = 1; i < numSets; ++i) { |
| MediaSet set = mediaSets.get(i); |
| if (set == mediaSet) { |
| mediaSets.set(i, setToFind); |
| indexToSwapTill = i; |
| break; |
| } |
| } |
| if (indexToSwapTill != Shared.INVALID) { |
| for (int i = indexToSwapTill; i > 1; --i) { |
| MediaSet setEnd = mediaSets.get(i); |
| MediaSet setPrev = mediaSets.get(i - 1); |
| mediaSets.set(i, setPrev); |
| mediaSets.set(i - 1, setEnd); |
| } |
| } |
| mMediaFeedNeedsToRun = true; |
| } |
| |
| public MediaSet replaceMediaSet(long setId, DataSource dataSource) { |
| Log.i(TAG, "Replacing media set " + setId); |
| final MediaSet set = getMediaSet(setId); |
| if (set != null) |
| set.refresh(); |
| return set; |
| } |
| |
| public void setSingleImageMode(boolean singleImageMode) { |
| mSingleImageMode = singleImageMode; |
| } |
| |
| public boolean isSingleImageMode() { |
| return mSingleImageMode; |
| } |
| |
| public MediaSet getExpandedMediaSet() { |
| if (mExpandedMediaSetIndex == Shared.INVALID) |
| return null; |
| if (mExpandedMediaSetIndex >= mMediaSets.size()) |
| return null; |
| return mMediaSets.get(mExpandedMediaSetIndex); |
| } |
| |
| public void refresh() { |
| if (mDataSource != null) { |
| synchronized (mRequestedRefresh) { |
| mRequestedRefresh.add(mDataSource.getDatabaseUris()); |
| } |
| } |
| } |
| |
| private void refresh(final String[] databaseUris) { |
| synchronized (mMediaSets) { |
| if (mDataSource != null) { |
| synchronized (mRequestedRefresh) { |
| mRequestedRefresh.add(databaseUris); |
| } |
| } |
| } |
| } |
| |
| public void onPause() { |
| final HashMap<String, ContentObserver> observers = mContentObservers; |
| final int numObservers = observers.size(); |
| if (numObservers > 0) { |
| String[] uris = new String[numObservers]; |
| final Set<String> keySet = observers.keySet(); |
| if (keySet != null) { |
| uris = keySet.toArray(uris); |
| final int numUris = uris.length; |
| final ContentResolver cr = mContext.getContentResolver(); |
| for (int i = 0; i < numUris; ++i) { |
| final String uri = uris[i]; |
| if (uri != null) { |
| final ContentObserver observer = observers.get(uri); |
| cr.unregisterContentObserver(observer); |
| observers.remove(uri); |
| } |
| } |
| } |
| } |
| observers.clear(); |
| } |
| |
| public void onResume() { |
| final Context context = mContext; |
| final DataSource dataSource = mDataSource; |
| if (context == null || dataSource == null) |
| return; |
| // We setup the listeners for this datasource |
| final String[] uris = dataSource.getDatabaseUris(); |
| final HashMap<String, ContentObserver> observers = mContentObservers; |
| if (context instanceof Gallery) { |
| final Gallery gallery = (Gallery) context; |
| final ContentResolver cr = context.getContentResolver(); |
| if (uris != null) { |
| final int numUris = uris.length; |
| for (int i = 0; i < numUris; ++i) { |
| final String uri = uris[i]; |
| final ContentObserver presentObserver = observers.get(uri); |
| if (presentObserver == null) { |
| final Handler handler = App.get(context).getHandler(); |
| final ContentObserver observer = new ContentObserver(handler) { |
| public void onChange(boolean selfChange) { |
| if (!mWaitingForMediaScanner) { |
| MediaFeed.this.refresh(new String[] { uri }); |
| } |
| } |
| }; |
| cr.registerContentObserver(Uri.parse(uri), true, observer); |
| observers.put(uri, observer); |
| } |
| } |
| } |
| } |
| refresh(); |
| } |
| } |