2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.gallery3d.data;
19 import android.net.Uri;
20 import android.provider.MediaStore;
22 import com.android.gallery3d.common.ApiHelper;
24 import java.lang.ref.SoftReference;
25 import java.util.ArrayList;
26 import java.util.Comparator;
27 import java.util.NoSuchElementException;
28 import java.util.SortedMap;
29 import java.util.TreeMap;
31 // MergeAlbum merges items from two or more MediaSets. It uses a Comparator to
32 // determine the order of items. The items are assumed to be sorted in the input
33 // media sets (with the same order that the Comparator uses).
35 // This only handles MediaItems, not SubMediaSets.
36 public class LocalMergeAlbum extends MediaSet implements ContentListener {
37 @SuppressWarnings("unused")
38 private static final String TAG = "LocalMergeAlbum";
39 private static final int PAGE_SIZE = 64;
41 private final Comparator<MediaItem> mComparator;
42 private final MediaSet[] mSources;
44 private FetchCache[] mFetcher;
45 private int mSupportedOperation;
46 private int mBucketId;
48 // mIndex maps global position to the position of each underlying media sets.
49 private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>();
51 public LocalMergeAlbum(
52 Path path, Comparator<MediaItem> comparator, MediaSet[] sources, int bucketId) {
53 super(path, INVALID_DATA_VERSION);
54 mComparator = comparator;
57 for (MediaSet set : mSources) {
58 set.addContentListener(this);
64 public boolean isCameraRoll() {
65 if (mSources.length == 0) return false;
66 for(MediaSet set : mSources) {
67 if (!set.isCameraRoll()) return false;
72 private void updateData() {
73 ArrayList<MediaSet> matches = new ArrayList<MediaSet>();
74 int supported = mSources.length == 0 ? 0 : MediaItem.SUPPORT_ALL;
75 mFetcher = new FetchCache[mSources.length];
76 for (int i = 0, n = mSources.length; i < n; ++i) {
77 mFetcher[i] = new FetchCache(mSources[i]);
78 supported &= mSources[i].getSupportedOperations();
80 mSupportedOperation = supported;
82 mIndex.put(0, new int[mSources.length]);
85 private void invalidateCache() {
86 for (int i = 0, n = mSources.length; i < n; i++) {
87 mFetcher[i].invalidate();
90 mIndex.put(0, new int[mSources.length]);
94 public Uri getContentUri() {
95 String bucketId = String.valueOf(mBucketId);
96 if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {
97 return MediaStore.Files.getContentUri("external").buildUpon()
98 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
101 // We don't have a single URL for a merged image before ICS
102 // So we used the image's URL as a substitute.
103 return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
104 .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
110 public String getName() {
111 return mSources.length == 0 ? "" : mSources[0].getName();
115 public int getMediaItemCount() {
116 return getTotalMediaItemCount();
120 public ArrayList<MediaItem> getMediaItem(int start, int count) {
122 // First find the nearest mark position <= start.
123 SortedMap<Integer, int[]> head = mIndex.headMap(start + 1);
124 int markPos = head.lastKey();
125 int[] subPos = head.get(markPos).clone();
126 MediaItem[] slot = new MediaItem[mSources.length];
128 int size = mSources.length;
131 for (int i = 0; i < size; i++) {
132 slot[i] = mFetcher[i].getItem(subPos[i]);
135 ArrayList<MediaItem> result = new ArrayList<MediaItem>();
137 for (int i = markPos; i < start + count; i++) {
138 int k = -1; // k points to the best slot up to now.
139 for (int j = 0; j < size; j++) {
140 if (slot[j] != null) {
141 if (k == -1 || mComparator.compare(slot[j], slot[k]) < 0) {
147 // If we don't have anything, all streams are exhausted.
150 // Pick the best slot and refill it.
155 slot[k] = mFetcher[k].getItem(subPos[k]);
157 // Periodically leave a mark in the index, so we can come back later.
158 if ((i + 1) % PAGE_SIZE == 0) {
159 mIndex.put(i + 1, subPos.clone());
167 public int getTotalMediaItemCount() {
169 for (MediaSet set : mSources) {
170 count += set.getTotalMediaItemCount();
176 public long reload() {
177 boolean changed = false;
178 for (int i = 0, n = mSources.length; i < n; ++i) {
179 if (mSources[i].reload() > mDataVersion) changed = true;
182 mDataVersion = nextVersionNumber();
190 public void onContentDirty() {
191 notifyContentChanged();
195 public int getSupportedOperations() {
196 return mSupportedOperation;
200 public void delete() {
201 for (MediaSet set : mSources) {
207 public void rotate(int degrees) {
208 for (MediaSet set : mSources) {
213 private static class FetchCache {
214 private MediaSet mBaseSet;
215 private SoftReference<ArrayList<MediaItem>> mCacheRef;
216 private int mStartPos;
218 public FetchCache(MediaSet baseSet) {
222 public void invalidate() {
226 public MediaItem getItem(int index) {
227 boolean needLoading = false;
228 ArrayList<MediaItem> cache = null;
229 if (mCacheRef == null
230 || index < mStartPos || index >= mStartPos + PAGE_SIZE) {
233 cache = mCacheRef.get();
240 cache = mBaseSet.getMediaItem(index, PAGE_SIZE);
241 mCacheRef = new SoftReference<ArrayList<MediaItem>>(cache);
245 if (index < mStartPos || index >= mStartPos + cache.size()) {
249 return cache.get(index - mStartPos);
254 public boolean isLeafAlbum() {