blob: 95146ae34efc3043f4173dde8826cede366847a5 [file] [log] [blame]
Owen Lin666ea1b2009-10-14 22:34:47 -07001/*
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
17package com.android.camera.gallery;
18
19import android.net.Uri;
20
21import com.android.camera.ImageManager;
22import com.android.camera.Util;
23
24import java.util.Arrays;
25import java.util.Comparator;
26import java.util.HashMap;
27import 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 */
34public 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}