blob: 95146ae34efc3043f4173dde8826cede366847a5 [file] [log] [blame]
/*
* 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();
}
}
}