1 /* 2 * Copyright (C) 2009 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.camera.gallery; 18 19 import android.net.Uri; 20 21 import com.android.camera.ImageManager; 22 import com.android.camera.Util; 23 24 import java.util.Arrays; 25 import java.util.Comparator; 26 import java.util.HashMap; 27 import java.util.PriorityQueue; 28 29 /** 30 * A union of different <code>IImageList</code>. This class can merge several 31 * <code>IImageList</code> into one list and sort them according to the 32 * timestamp (The sorting must be same as all the given lists). 33 */ 34 public class ImageListUber implements IImageList { 35 @SuppressWarnings("unused") 36 private static final String TAG = "ImageListUber"; 37 38 private final IImageList [] mSubList; 39 private final PriorityQueue<MergeSlot> mQueue; 40 41 // This is an array of Longs wherein each Long consists of two components: 42 // "a number" and "an index of sublist". 43 // * The lower 32bit indicates the number of consecutive entries that 44 // belong to a given sublist. 45 // 46 // * The higher 32bit component indicates which sublist we're referring 47 // to. 48 private long[] mSkipList; 49 private int mSkipListSize; 50 private int [] mSkipCounts; 51 private int mLastListIndex; 52 53 public ImageListUber(IImageList [] sublist, int sort) { 54 mSubList = sublist.clone(); 55 mQueue = new PriorityQueue<MergeSlot>(4, 56 sort == ImageManager.SORT_ASCENDING 57 ? new AscendingComparator() 58 : new DescendingComparator()); 59 mSkipList = new long[16]; 60 mSkipListSize = 0; 61 mSkipCounts = new int[mSubList.length]; 62 mLastListIndex = -1; 63 mQueue.clear(); 64 for (int i = 0, n = mSubList.length; i < n; ++i) { 65 IImageList list = mSubList[i]; 66 MergeSlot slot = new MergeSlot(list, i); 67 if (slot.next()) mQueue.add(slot); 68 } 69 } 70 71 public HashMap<String, String> getBucketIds() { 72 HashMap<String, String> hashMap = new HashMap<String, String>(); 73 for (IImageList list : mSubList) { 74 hashMap.putAll(list.getBucketIds()); 75 } 76 return hashMap; 77 } 78 79 public int getCount() { 80 int count = 0; 81 for (IImageList subList : mSubList) { 82 count += subList.getCount(); 83 } 84 return count; 85 } 86 87 public boolean isEmpty() { 88 for (IImageList subList : mSubList) { 89 if (!subList.isEmpty()) return false; 90 } 91 return true; 92 } 93 94 // mSkipCounts is used to tally the counts as we traverse 95 // the mSkipList. It's a member variable only so that 96 // we don't have to allocate each time through. Otherwise 97 // it could just as easily be a local. 98 99 public IImage getImageAt(int index) { 100 if (index < 0 || index > getCount()) { 101 throw new IndexOutOfBoundsException( 102 "index " + index + " out of range max is " + getCount()); 103 } 104 105 int skipCounts[] = mSkipCounts; 106 // zero out the mSkipCounts since that's only used for the 107 // duration of the function call. 108 Arrays.fill(skipCounts, 0); 109 110 // a counter of how many images we've skipped in 111 // trying to get to index. alternatively we could 112 // have decremented index but, alas, I liked this 113 // way more. 114 int skipCount = 0; 115 116 // scan the existing mSkipList to see if we've computed 117 // enough to just return the answer 118 for (int i = 0, n = mSkipListSize; i < n; ++i) { 119 long v = mSkipList[i]; 120 121 int offset = (int) (v & 0xFFFFFFFF); 122 int which = (int) (v >> 32); 123 if (skipCount + offset > index) { 124 int subindex = mSkipCounts[which] + (index - skipCount); 125 return mSubList[which].getImageAt(subindex); 126 } 127 skipCount += offset; 128 mSkipCounts[which] += offset; 129 } 130 131 for (; true; ++skipCount) { 132 MergeSlot slot = nextMergeSlot(); 133 if (slot == null) return null; 134 if (skipCount == index) { 135 IImage result = slot.mImage; 136 if (slot.next()) mQueue.add(slot); 137 return result; 138 } 139 if (slot.next()) mQueue.add(slot); 140 } 141 } 142 143 private MergeSlot nextMergeSlot() { 144 MergeSlot slot = mQueue.poll(); 145 if (slot == null) return null; 146 if (slot.mListIndex == mLastListIndex) { 147 int lastIndex = mSkipListSize - 1; 148 ++mSkipList[lastIndex]; 149 } else { 150 mLastListIndex = slot.mListIndex; 151 if (mSkipList.length == mSkipListSize) { 152 long [] temp = new long[mSkipListSize * 2]; 153 System.arraycopy(mSkipList, 0, temp, 0, mSkipListSize); 154 mSkipList = temp; 155 } 156 mSkipList[mSkipListSize++] = (((long) mLastListIndex) << 32) | 1; 157 } 158 return slot; 159 } 160 161 public IImage getImageForUri(Uri uri) { 162 for (IImageList sublist : mSubList) { 163 IImage image = sublist.getImageForUri(uri); 164 if (image != null) return image; 165 } 166 return null; 167 } 168 169 /** 170 * Modify the skip list when an image is deleted by finding 171 * the relevant entry in mSkipList and decrementing the 172 * counter. This is simple because deletion can never 173 * cause change the order of images. 174 */ 175 private void modifySkipCountForDeletedImage(int index) { 176 int skipCount = 0; 177 for (int i = 0, n = mSkipListSize; i < n; i++) { 178 long v = mSkipList[i]; 179 int offset = (int) (v & 0xFFFFFFFF); 180 if (skipCount + offset > index) { 181 mSkipList[i] = v - 1; 182 break; 183 } 184 skipCount += offset; 185 } 186 } 187 188 private boolean removeImage(IImage image, int index) { 189 IImageList list = image.getContainer(); 190 if (list != null && list.removeImage(image)) { 191 modifySkipCountForDeletedImage(index); 192 return true; 193 } 194 return false; 195 } 196 197 public boolean removeImage(IImage image) { 198 return removeImage(image, getImageIndex(image)); 199 } 200 201 public boolean removeImageAt(int index) { 202 IImage image = getImageAt(index); 203 if (image == null) return false; 204 return removeImage(image, index); 205 } 206 207 public synchronized int getImageIndex(IImage image) { 208 IImageList list = image.getContainer(); 209 int listIndex = Util.indexOf(mSubList, list); 210 if (listIndex == -1) { 211 throw new IllegalArgumentException(); 212 } 213 int listOffset = list.getImageIndex(image); 214 215 // Similar algorithm as getImageAt(int index) 216 int skipCount = 0; 217 for (int i = 0, n = mSkipListSize; i < n; ++i) { 218 long value = mSkipList[i]; 219 int offset = (int) (value & 0xFFFFFFFF); 220 int which = (int) (value >> 32); 221 if (which == listIndex) { 222 if (listOffset < offset) { 223 return skipCount + listOffset; 224 } 225 listOffset -= offset; 226 } 227 skipCount += offset; 228 } 229 230 for (; true; ++skipCount) { 231 MergeSlot slot = nextMergeSlot(); 232 if (slot == null) return -1; 233 if (slot.mImage == image) { 234 if (slot.next()) mQueue.add(slot); 235 return skipCount; 236 } 237 if (slot.next()) mQueue.add(slot); 238 } 239 } 240 241 private static class DescendingComparator implements Comparator<MergeSlot> { 242 243 public int compare(MergeSlot m1, MergeSlot m2) { 244 if (m1.mDateTaken != m2.mDateTaken) { 245 return m1.mDateTaken < m2.mDateTaken ? 1 : -1; 246 } 247 return m1.mListIndex - m2.mListIndex; 248 } 249 } 250 251 private static class AscendingComparator implements Comparator<MergeSlot> { 252 253 public int compare(MergeSlot m1, MergeSlot m2) { 254 if (m1.mDateTaken != m2.mDateTaken) { 255 return m1.mDateTaken < m2.mDateTaken ? -1 : 1; 256 } 257 return m1.mListIndex - m2.mListIndex; 258 } 259 } 260 261 /** 262 * A merging slot is used to trace the current position of a sublist. For 263 * each given sub list, there will be one corresponding merge slot. We 264 * use merge-sort-like algorithm to build the merged list. At begining, 265 * we put all the slots in a sorted heap (by timestamp). Each time, we 266 * pop the slot with earliest timestamp out, get the image, and then move 267 * the index forward, and put it back to the heap. 268 */ 269 private static class MergeSlot { 270 private int mOffset = -1; 271 private final IImageList mList; 272 273 int mListIndex; 274 long mDateTaken; 275 IImage mImage; 276 277 public MergeSlot(IImageList list, int index) { 278 mList = list; 279 mListIndex = index; 280 } 281 282 public boolean next() { 283 if (mOffset >= mList.getCount() - 1) return false; 284 mImage = mList.getImageAt(++mOffset); 285 mDateTaken = mImage.getDateTaken(); 286 return true; 287 } 288 } 289 290 public void close() { 291 for (int i = 0, n = mSubList.length; i < n; ++i) { 292 mSubList[i].close(); 293 } 294 } 295 } 296