| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.camera.gallery; |
| |
| import android.net.Uri; |
| |
| import com.android.camera.ImageManager; |
| import com.android.camera.Util; |
| |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.PriorityQueue; |
| |
| /** |
| * A union of different <code>IImageList</code>. This class can merge several |
| * <code>IImageList</code> into one list and sort them according to the |
| * timestamp (The sorting must be same as all the given lists). |
| */ |
| public class ImageListUber implements IImageList { |
| @SuppressWarnings("unused") |
| private static final String TAG = "ImageListUber"; |
| |
| private final IImageList [] mSubList; |
| private final PriorityQueue<MergeSlot> mQueue; |
| |
| // This is an array of Longs wherein each Long consists of two components: |
| // "a number" and "an index of sublist". |
| // * The lower 32bit indicates the number of consecutive entries that |
| // belong to a given sublist. |
| // |
| // * The higher 32bit component indicates which sublist we're referring |
| // to. |
| private long[] mSkipList; |
| private int mSkipListSize; |
| private int [] mSkipCounts; |
| private int mLastListIndex; |
| |
| public ImageListUber(IImageList [] sublist, int sort) { |
| mSubList = sublist.clone(); |
| mQueue = new PriorityQueue<MergeSlot>(4, |
| sort == ImageManager.SORT_ASCENDING |
| ? new AscendingComparator() |
| : new DescendingComparator()); |
| mSkipList = new long[16]; |
| mSkipListSize = 0; |
| mSkipCounts = new int[mSubList.length]; |
| mLastListIndex = -1; |
| mQueue.clear(); |
| for (int i = 0, n = mSubList.length; i < n; ++i) { |
| IImageList list = mSubList[i]; |
| MergeSlot slot = new MergeSlot(list, i); |
| if (slot.next()) mQueue.add(slot); |
| } |
| } |
| |
| public HashMap<String, String> getBucketIds() { |
| HashMap<String, String> hashMap = new HashMap<String, String>(); |
| for (IImageList list : mSubList) { |
| hashMap.putAll(list.getBucketIds()); |
| } |
| return hashMap; |
| } |
| |
| public int getCount() { |
| int count = 0; |
| for (IImageList subList : mSubList) { |
| count += subList.getCount(); |
| } |
| return count; |
| } |
| |
| public boolean isEmpty() { |
| for (IImageList subList : mSubList) { |
| if (!subList.isEmpty()) return false; |
| } |
| return true; |
| } |
| |
| // mSkipCounts is used to tally the counts as we traverse |
| // the mSkipList. It's a member variable only so that |
| // we don't have to allocate each time through. Otherwise |
| // it could just as easily be a local. |
| |
| public IImage getImageAt(int index) { |
| if (index < 0 || index > getCount()) { |
| throw new IndexOutOfBoundsException( |
| "index " + index + " out of range max is " + getCount()); |
| } |
| |
| int skipCounts[] = mSkipCounts; |
| // zero out the mSkipCounts since that's only used for the |
| // duration of the function call. |
| Arrays.fill(skipCounts, 0); |
| |
| // a counter of how many images we've skipped in |
| // trying to get to index. alternatively we could |
| // have decremented index but, alas, I liked this |
| // way more. |
| int skipCount = 0; |
| |
| // scan the existing mSkipList to see if we've computed |
| // enough to just return the answer |
| for (int i = 0, n = mSkipListSize; i < n; ++i) { |
| long v = mSkipList[i]; |
| |
| int offset = (int) (v & 0xFFFFFFFF); |
| int which = (int) (v >> 32); |
| if (skipCount + offset > index) { |
| int subindex = mSkipCounts[which] + (index - skipCount); |
| return mSubList[which].getImageAt(subindex); |
| } |
| skipCount += offset; |
| mSkipCounts[which] += offset; |
| } |
| |
| for (; true; ++skipCount) { |
| MergeSlot slot = nextMergeSlot(); |
| if (slot == null) return null; |
| if (skipCount == index) { |
| IImage result = slot.mImage; |
| if (slot.next()) mQueue.add(slot); |
| return result; |
| } |
| if (slot.next()) mQueue.add(slot); |
| } |
| } |
| |
| private MergeSlot nextMergeSlot() { |
| MergeSlot slot = mQueue.poll(); |
| if (slot == null) return null; |
| if (slot.mListIndex == mLastListIndex) { |
| int lastIndex = mSkipListSize - 1; |
| ++mSkipList[lastIndex]; |
| } else { |
| mLastListIndex = slot.mListIndex; |
| if (mSkipList.length == mSkipListSize) { |
| long [] temp = new long[mSkipListSize * 2]; |
| System.arraycopy(mSkipList, 0, temp, 0, mSkipListSize); |
| mSkipList = temp; |
| } |
| mSkipList[mSkipListSize++] = (((long) mLastListIndex) << 32) | 1; |
| } |
| return slot; |
| } |
| |
| public IImage getImageForUri(Uri uri) { |
| for (IImageList sublist : mSubList) { |
| IImage image = sublist.getImageForUri(uri); |
| if (image != null) return image; |
| } |
| return null; |
| } |
| |
| /** |
| * Modify the skip list when an image is deleted by finding |
| * the relevant entry in mSkipList and decrementing the |
| * counter. This is simple because deletion can never |
| * cause change the order of images. |
| */ |
| private void modifySkipCountForDeletedImage(int index) { |
| int skipCount = 0; |
| for (int i = 0, n = mSkipListSize; i < n; i++) { |
| long v = mSkipList[i]; |
| int offset = (int) (v & 0xFFFFFFFF); |
| if (skipCount + offset > index) { |
| mSkipList[i] = v - 1; |
| break; |
| } |
| skipCount += offset; |
| } |
| } |
| |
| private boolean removeImage(IImage image, int index) { |
| IImageList list = image.getContainer(); |
| if (list != null && list.removeImage(image)) { |
| modifySkipCountForDeletedImage(index); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean removeImage(IImage image) { |
| return removeImage(image, getImageIndex(image)); |
| } |
| |
| public boolean removeImageAt(int index) { |
| IImage image = getImageAt(index); |
| if (image == null) return false; |
| return removeImage(image, index); |
| } |
| |
| public synchronized int getImageIndex(IImage image) { |
| IImageList list = image.getContainer(); |
| int listIndex = Util.indexOf(mSubList, list); |
| if (listIndex == -1) { |
| throw new IllegalArgumentException(); |
| } |
| int listOffset = list.getImageIndex(image); |
| |
| // Similar algorithm as getImageAt(int index) |
| int skipCount = 0; |
| for (int i = 0, n = mSkipListSize; i < n; ++i) { |
| long value = mSkipList[i]; |
| int offset = (int) (value & 0xFFFFFFFF); |
| int which = (int) (value >> 32); |
| if (which == listIndex) { |
| if (listOffset < offset) { |
| return skipCount + listOffset; |
| } |
| listOffset -= offset; |
| } |
| skipCount += offset; |
| } |
| |
| for (; true; ++skipCount) { |
| MergeSlot slot = nextMergeSlot(); |
| if (slot == null) return -1; |
| if (slot.mImage == image) { |
| if (slot.next()) mQueue.add(slot); |
| return skipCount; |
| } |
| if (slot.next()) mQueue.add(slot); |
| } |
| } |
| |
| private static class DescendingComparator implements Comparator<MergeSlot> { |
| |
| public int compare(MergeSlot m1, MergeSlot m2) { |
| if (m1.mDateTaken != m2.mDateTaken) { |
| return m1.mDateTaken < m2.mDateTaken ? 1 : -1; |
| } |
| return m1.mListIndex - m2.mListIndex; |
| } |
| } |
| |
| private static class AscendingComparator implements Comparator<MergeSlot> { |
| |
| public int compare(MergeSlot m1, MergeSlot m2) { |
| if (m1.mDateTaken != m2.mDateTaken) { |
| return m1.mDateTaken < m2.mDateTaken ? -1 : 1; |
| } |
| return m1.mListIndex - m2.mListIndex; |
| } |
| } |
| |
| /** |
| * A merging slot is used to trace the current position of a sublist. For |
| * each given sub list, there will be one corresponding merge slot. We |
| * use merge-sort-like algorithm to build the merged list. At begining, |
| * we put all the slots in a sorted heap (by timestamp). Each time, we |
| * pop the slot with earliest timestamp out, get the image, and then move |
| * the index forward, and put it back to the heap. |
| */ |
| private static class MergeSlot { |
| private int mOffset = -1; |
| private final IImageList mList; |
| |
| int mListIndex; |
| long mDateTaken; |
| IImage mImage; |
| |
| public MergeSlot(IImageList list, int index) { |
| mList = list; |
| mListIndex = index; |
| } |
| |
| public boolean next() { |
| if (mOffset >= mList.getCount() - 1) return false; |
| mImage = mList.getImageAt(++mOffset); |
| mDateTaken = mImage.getDateTaken(); |
| return true; |
| } |
| } |
| |
| public void close() { |
| for (int i = 0, n = mSubList.length; i < n; ++i) { |
| mSubList[i].close(); |
| } |
| } |
| } |