diff options
Diffstat (limited to 'src/com/android/gallery3d/data/LocalMergeAlbum.java')
-rw-r--r-- | src/com/android/gallery3d/data/LocalMergeAlbum.java | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java new file mode 100644 index 000000000..f0b5e5726 --- /dev/null +++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2010 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.android.gallery3d.data; + +import android.net.Uri; +import android.provider.MediaStore; + +import com.android.gallery3d.common.ApiHelper; + +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.TreeMap; + +// MergeAlbum merges items from two or more MediaSets. It uses a Comparator to +// determine the order of items. The items are assumed to be sorted in the input +// media sets (with the same order that the Comparator uses). +// +// This only handles MediaItems, not SubMediaSets. +public class LocalMergeAlbum extends MediaSet implements ContentListener { + @SuppressWarnings("unused") + private static final String TAG = "LocalMergeAlbum"; + private static final int PAGE_SIZE = 64; + + private final Comparator<MediaItem> mComparator; + private final MediaSet[] mSources; + + private FetchCache[] mFetcher; + private int mSupportedOperation; + private int mBucketId; + + // mIndex maps global position to the position of each underlying media sets. + private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>(); + + public LocalMergeAlbum( + Path path, Comparator<MediaItem> comparator, MediaSet[] sources, int bucketId) { + super(path, INVALID_DATA_VERSION); + mComparator = comparator; + mSources = sources; + mBucketId = bucketId; + for (MediaSet set : mSources) { + set.addContentListener(this); + } + reload(); + } + + @Override + public boolean isCameraRoll() { + if (mSources.length == 0) return false; + for(MediaSet set : mSources) { + if (!set.isCameraRoll()) return false; + } + return true; + } + + private void updateData() { + ArrayList<MediaSet> matches = new ArrayList<MediaSet>(); + int supported = mSources.length == 0 ? 0 : MediaItem.SUPPORT_ALL; + mFetcher = new FetchCache[mSources.length]; + for (int i = 0, n = mSources.length; i < n; ++i) { + mFetcher[i] = new FetchCache(mSources[i]); + supported &= mSources[i].getSupportedOperations(); + } + mSupportedOperation = supported; + mIndex.clear(); + mIndex.put(0, new int[mSources.length]); + } + + private void invalidateCache() { + for (int i = 0, n = mSources.length; i < n; i++) { + mFetcher[i].invalidate(); + } + mIndex.clear(); + mIndex.put(0, new int[mSources.length]); + } + + @Override + public Uri getContentUri() { + String bucketId = String.valueOf(mBucketId); + if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) { + return MediaStore.Files.getContentUri("external").buildUpon() + .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId) + .build(); + } else { + // We don't have a single URL for a merged image before ICS + // So we used the image's URL as a substitute. + return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon() + .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId) + .build(); + } + } + + @Override + public String getName() { + return mSources.length == 0 ? "" : mSources[0].getName(); + } + + @Override + public int getMediaItemCount() { + return getTotalMediaItemCount(); + } + + @Override + public ArrayList<MediaItem> getMediaItem(int start, int count) { + + // First find the nearest mark position <= start. + SortedMap<Integer, int[]> head = mIndex.headMap(start + 1); + int markPos = head.lastKey(); + int[] subPos = head.get(markPos).clone(); + MediaItem[] slot = new MediaItem[mSources.length]; + + int size = mSources.length; + + // fill all slots + for (int i = 0; i < size; i++) { + slot[i] = mFetcher[i].getItem(subPos[i]); + } + + ArrayList<MediaItem> result = new ArrayList<MediaItem>(); + + for (int i = markPos; i < start + count; i++) { + int k = -1; // k points to the best slot up to now. + for (int j = 0; j < size; j++) { + if (slot[j] != null) { + if (k == -1 || mComparator.compare(slot[j], slot[k]) < 0) { + k = j; + } + } + } + + // If we don't have anything, all streams are exhausted. + if (k == -1) break; + + // Pick the best slot and refill it. + subPos[k]++; + if (i >= start) { + result.add(slot[k]); + } + slot[k] = mFetcher[k].getItem(subPos[k]); + + // Periodically leave a mark in the index, so we can come back later. + if ((i + 1) % PAGE_SIZE == 0) { + mIndex.put(i + 1, subPos.clone()); + } + } + + return result; + } + + @Override + public int getTotalMediaItemCount() { + int count = 0; + for (MediaSet set : mSources) { + count += set.getTotalMediaItemCount(); + } + return count; + } + + @Override + public long reload() { + boolean changed = false; + for (int i = 0, n = mSources.length; i < n; ++i) { + if (mSources[i].reload() > mDataVersion) changed = true; + } + if (changed) { + mDataVersion = nextVersionNumber(); + updateData(); + invalidateCache(); + } + return mDataVersion; + } + + @Override + public void onContentDirty() { + notifyContentChanged(); + } + + @Override + public int getSupportedOperations() { + return mSupportedOperation; + } + + @Override + public void delete() { + for (MediaSet set : mSources) { + set.delete(); + } + } + + @Override + public void rotate(int degrees) { + for (MediaSet set : mSources) { + set.rotate(degrees); + } + } + + private static class FetchCache { + private MediaSet mBaseSet; + private SoftReference<ArrayList<MediaItem>> mCacheRef; + private int mStartPos; + + public FetchCache(MediaSet baseSet) { + mBaseSet = baseSet; + } + + public void invalidate() { + mCacheRef = null; + } + + public MediaItem getItem(int index) { + boolean needLoading = false; + ArrayList<MediaItem> cache = null; + if (mCacheRef == null + || index < mStartPos || index >= mStartPos + PAGE_SIZE) { + needLoading = true; + } else { + cache = mCacheRef.get(); + if (cache == null) { + needLoading = true; + } + } + + if (needLoading) { + cache = mBaseSet.getMediaItem(index, PAGE_SIZE); + mCacheRef = new SoftReference<ArrayList<MediaItem>>(cache); + mStartPos = index; + } + + if (index < mStartPos || index >= mStartPos + cache.size()) { + return null; + } + + return cache.get(index - mStartPos); + } + } + + @Override + public boolean isLeafAlbum() { + return true; + } +} |