X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;ds=sidebyside;f=src%2Fcom%2Fcooliris%2Fmedia%2FMediaFeed.java;fp=src%2Fcom%2Fcooliris%2Fmedia%2FMediaFeed.java;h=62fa0840fda38ee56b9b1a389585655357d5ca8f;hb=c145d41ec9e768e61042d0e765d2aa4cca93574d;hp=0000000000000000000000000000000000000000;hpb=e927ab54062112c88aa17afb90657f4e7f54e529;p=android-x86%2Fpackages-apps-Gallery2.git diff --git a/src/com/cooliris/media/MediaFeed.java b/src/com/cooliris/media/MediaFeed.java new file mode 100644 index 000000000..62fa0840f --- /dev/null +++ b/src/com/cooliris/media/MediaFeed.java @@ -0,0 +1,743 @@ +package com.cooliris.media; + +import java.util.ArrayList; +import java.util.HashMap; + +import android.content.Context; +import android.view.Gravity; +import android.widget.Toast; +import android.os.Process; + +import com.cooliris.media.MediaClustering.Cluster; + +public final class MediaFeed implements Runnable { + 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 IndexRange mVisibleRange = new IndexRange(); + private IndexRange mBufferedRange = new IndexRange(); + private ArrayList mMediaSets = new ArrayList(); + 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 mClusterSets = new HashMap(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; + + 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() { + if (mDataSourceThread != null) { + mDataSource.shutdown(); + mDataSourceThread.interrupt(); + mDataSourceThread = null; + } + if (mAlbumSourceThread != null) { + mAlbumSourceThread.interrupt(); + 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; + } + + 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 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++) { + if (mMediaSets.get(i).mId == setId) { + return mMediaSets.get(i); + } + } + } 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 getClustersForSet(final MediaSet set) { + ArrayList 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 mediaBuckets, final Object data) { + int numBuckets = mediaBuckets.size(); + final ArrayList copyMediaBuckets = new ArrayList(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 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 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 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.getNumItems(); + } + } 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 MediaSet getSetForSlot(int slotIndex) { + if (slotIndex < 0) { + return null; + } + + ArrayList 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 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 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; + mLoading = true; + mDataSourceThread = new Thread(this); + mDataSourceThread.setName("MediaFeed"); + 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) { + dataSource.loadMediaSets(feed); + } + 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(R.string.initializing), Toast.LENGTH_LONG); + Thread.sleep(6000); + } catch (InterruptedException e) { + return; + } + } + if (mWaitingForMediaScanner) { + showToast(mContext.getResources().getString(R.string.loading_new), Toast.LENGTH_LONG); + mWaitingForMediaScanner = false; + if (dataSource != null) { + dataSource.loadMediaSets(feed); + } + } + mLoading = false; + } + }); + mAlbumSourceThread.setName("MediaSets"); + mAlbumSourceThread.start(); + } + + 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 && !((Gallery) mContext).isPaused()) { + ((Gallery) 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()) { + if (mListenerNeedsUpdate) { + mListenerNeedsUpdate = false; + if (mListener != null) + mListener.onFeedChanged(this, mListenerNeedsLayout); + try { + Thread.sleep(sleepMs); + } catch (InterruptedException e) { + return; + } + } else { + if (mWaitingForMediaScanner) { + synchronized (mMediaSets) { + mMediaSets.clear(); + } + } + try { + Thread.sleep(sleepMs); + } catch (InterruptedException e) { + return; + } + } + sleepMs = 300; + if (!mMediaFeedNeedsToRun) + continue; + if (((Gallery) mContext).isPaused()) + continue; + mMediaFeedNeedsToRun = false; + ArrayList 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) { + dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); + if (set.getNumExpectedItems() == 0) { + mediaSets.remove(set); + break; + } + if (mListener != null) { + mListener.onFeedChanged(this, false); + } + sleepMs = 100; + scanMediaSets = false; + } + if (!set.setContainsValidItems()) { + mediaSets.remove(set); + if (mListener != null) { + mListener.onFeedChanged(this, 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) { + dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); + if (set.getNumExpectedItems() == 0) { + mediaSets.remove(set); + break; + } + if (mListener != null) { + mListener.onFeedChanged(this, false); + } + sleepMs = 100; + scanMediaSets = false; + } + } + } else if (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.getNumItems() != 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 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. + dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD) + * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD); + if (set.getNumExpectedItems() == 0) { + mediaSets.remove(set); + mListener.onFeedChanged(this, false); + } + if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) { + mListener.onFeedChanged(this, false); + } + } + } + MediaFilter filter = mMediaFilter; + if (filter != null && mMediaFilteredSet == null) { + if (expandedSetIndex != Shared.INVALID) { + MediaSet set = mediaSets.get(expandedSetIndex); + ArrayList 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; + mMediaFeedNeedsToRun = true; + updateListener(true); + } + } + + public void moveSetToFront(MediaSet mediaSet) { + ArrayList 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) { + MediaSet mediaSet = new MediaSet(dataSource); + mediaSet.mId = setId; + ArrayList mediaSets = mMediaSets; + int numSets = mediaSets.size(); + for (int i = 0; i < numSets; ++i) { + final MediaSet thisSet = mediaSets.get(i); + if (thisSet.mId == setId) { + mediaSet.mName = thisSet.mName; + mediaSet.mHasImages = thisSet.mHasImages; + mediaSet.mHasVideos = thisSet.mHasVideos; + mediaSets.set(i, mediaSet); + break; + } + } + mMediaFeedNeedsToRun = true; + return mediaSet; + } + + 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); + } +}