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