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 
ImageListUber(IImageList [] sublist, int sort)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 
getBucketIds()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 
getCount()79     public int getCount() {
80         int count = 0;
81         for (IImageList subList : mSubList) {
82             count += subList.getCount();
83         }
84         return count;
85     }
86 
isEmpty()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 
getImageAt(int index)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 
nextMergeSlot()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 
getImageForUri(Uri uri)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      */
modifySkipCountForDeletedImage(int index)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 
removeImage(IImage image, int index)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 
removeImage(IImage image)197     public boolean removeImage(IImage image) {
198         return removeImage(image, getImageIndex(image));
199     }
200 
removeImageAt(int index)201     public boolean removeImageAt(int index) {
202         IImage image = getImageAt(index);
203         if (image == null) return false;
204         return removeImage(image, index);
205     }
206 
getImageIndex(IImage image)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 
compare(MergeSlot m1, MergeSlot m2)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 
compare(MergeSlot m1, MergeSlot m2)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 
MergeSlot(IImageList list, int index)277         public MergeSlot(IImageList list, int index) {
278             mList = list;
279             mListIndex = index;
280         }
281 
next()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 
close()290     public void close() {
291         for (int i = 0, n = mSubList.length; i < n; ++i) {
292             mSubList[i].close();
293         }
294     }
295 }
296