1/* 2 * Copyright (C) 2010 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 17package com.android.gallery3d.data; 18 19import android.net.Uri; 20import android.provider.MediaStore; 21 22import java.lang.ref.SoftReference; 23import java.util.ArrayList; 24import java.util.Comparator; 25import java.util.SortedMap; 26import java.util.TreeMap; 27 28// MergeAlbum merges items from two or more MediaSets. It uses a Comparator to 29// determine the order of items. The items are assumed to be sorted in the input 30// media sets (with the same order that the Comparator uses). 31// 32// This only handles MediaItems, not SubMediaSets. 33public class LocalMergeAlbum extends MediaSet implements ContentListener { 34 @SuppressWarnings("unused") 35 private static final String TAG = "LocalMergeAlbum"; 36 private static final int PAGE_SIZE = 64; 37 38 private final Comparator<MediaItem> mComparator; 39 private final MediaSet[] mSources; 40 41 private String mName; 42 private FetchCache[] mFetcher; 43 private int mSupportedOperation; 44 private int mBucketId; 45 46 // mIndex maps global position to the position of each underlying media sets. 47 private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>(); 48 49 public LocalMergeAlbum( 50 Path path, Comparator<MediaItem> comparator, MediaSet[] sources, int bucketId) { 51 super(path, INVALID_DATA_VERSION); 52 mComparator = comparator; 53 mSources = sources; 54 mName = sources.length == 0 ? "" : sources[0].getName(); 55 mBucketId = bucketId; 56 for (MediaSet set : mSources) { 57 set.addContentListener(this); 58 } 59 } 60 61 private void updateData() { 62 ArrayList<MediaSet> matches = new ArrayList<MediaSet>(); 63 int supported = mSources.length == 0 ? 0 : MediaItem.SUPPORT_ALL; 64 mFetcher = new FetchCache[mSources.length]; 65 for (int i = 0, n = mSources.length; i < n; ++i) { 66 mFetcher[i] = new FetchCache(mSources[i]); 67 supported &= mSources[i].getSupportedOperations(); 68 } 69 mSupportedOperation = supported; 70 mIndex.clear(); 71 mIndex.put(0, new int[mSources.length]); 72 mName = mSources.length == 0 ? "" : mSources[0].getName(); 73 } 74 75 private void invalidateCache() { 76 for (int i = 0, n = mSources.length; i < n; i++) { 77 mFetcher[i].invalidate(); 78 } 79 mIndex.clear(); 80 mIndex.put(0, new int[mSources.length]); 81 } 82 83 @Override 84 public Uri getContentUri() { 85 return MediaStore.Files.getContentUri("external").buildUpon().appendQueryParameter( 86 LocalSource.KEY_BUCKET_ID, String.valueOf(mBucketId)).build(); 87 } 88 89 @Override 90 public String getName() { 91 return mName; 92 } 93 94 @Override 95 public int getMediaItemCount() { 96 return getTotalMediaItemCount(); 97 } 98 99 @Override 100 public ArrayList<MediaItem> getMediaItem(int start, int count) { 101 102 // First find the nearest mark position <= start. 103 SortedMap<Integer, int[]> head = mIndex.headMap(start + 1); 104 int markPos = head.lastKey(); 105 int[] subPos = head.get(markPos).clone(); 106 MediaItem[] slot = new MediaItem[mSources.length]; 107 108 int size = mSources.length; 109 110 // fill all slots 111 for (int i = 0; i < size; i++) { 112 slot[i] = mFetcher[i].getItem(subPos[i]); 113 } 114 115 ArrayList<MediaItem> result = new ArrayList<MediaItem>(); 116 117 for (int i = markPos; i < start + count; i++) { 118 int k = -1; // k points to the best slot up to now. 119 for (int j = 0; j < size; j++) { 120 if (slot[j] != null) { 121 if (k == -1 || mComparator.compare(slot[j], slot[k]) < 0) { 122 k = j; 123 } 124 } 125 } 126 127 // If we don't have anything, all streams are exhausted. 128 if (k == -1) break; 129 130 // Pick the best slot and refill it. 131 subPos[k]++; 132 if (i >= start) { 133 result.add(slot[k]); 134 } 135 slot[k] = mFetcher[k].getItem(subPos[k]); 136 137 // Periodically leave a mark in the index, so we can come back later. 138 if ((i + 1) % PAGE_SIZE == 0) { 139 mIndex.put(i + 1, subPos.clone()); 140 } 141 } 142 143 return result; 144 } 145 146 @Override 147 public int getTotalMediaItemCount() { 148 int count = 0; 149 for (MediaSet set : mSources) { 150 count += set.getTotalMediaItemCount(); 151 } 152 return count; 153 } 154 155 @Override 156 public long reload() { 157 boolean changed = false; 158 for (int i = 0, n = mSources.length; i < n; ++i) { 159 if (mSources[i].reload() > mDataVersion) changed = true; 160 } 161 if (changed) { 162 mDataVersion = nextVersionNumber(); 163 updateData(); 164 invalidateCache(); 165 } 166 return mDataVersion; 167 } 168 169 @Override 170 public void onContentDirty() { 171 notifyContentChanged(); 172 } 173 174 @Override 175 public int getSupportedOperations() { 176 return mSupportedOperation; 177 } 178 179 @Override 180 public void delete() { 181 for (MediaSet set : mSources) { 182 set.delete(); 183 } 184 } 185 186 @Override 187 public void rotate(int degrees) { 188 for (MediaSet set : mSources) { 189 set.rotate(degrees); 190 } 191 } 192 193 private static class FetchCache { 194 private MediaSet mBaseSet; 195 private SoftReference<ArrayList<MediaItem>> mCacheRef; 196 private int mStartPos; 197 198 public FetchCache(MediaSet baseSet) { 199 mBaseSet = baseSet; 200 } 201 202 public void invalidate() { 203 mCacheRef = null; 204 } 205 206 public MediaItem getItem(int index) { 207 boolean needLoading = false; 208 ArrayList<MediaItem> cache = null; 209 if (mCacheRef == null 210 || index < mStartPos || index >= mStartPos + PAGE_SIZE) { 211 needLoading = true; 212 } else { 213 cache = mCacheRef.get(); 214 if (cache == null) { 215 needLoading = true; 216 } 217 } 218 219 if (needLoading) { 220 cache = mBaseSet.getMediaItem(index, PAGE_SIZE); 221 mCacheRef = new SoftReference<ArrayList<MediaItem>>(cache); 222 mStartPos = index; 223 } 224 225 if (index < mStartPos || index >= mStartPos + cache.size()) { 226 return null; 227 } 228 229 return cache.get(index - mStartPos); 230 } 231 } 232 233 @Override 234 public boolean isLeafAlbum() { 235 return true; 236 } 237} 238