Home | History | Annotate | Download | only in gallery
      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