Home | History | Annotate | Download | only in data
      1 package com.android.gallery3d.ingest.data;
      2 
      3 import android.annotation.TargetApi;
      4 import android.mtp.MtpConstants;
      5 import android.mtp.MtpDevice;
      6 import android.mtp.MtpObjectInfo;
      7 import android.os.Build;
      8 
      9 import java.util.ArrayList;
     10 import java.util.Arrays;
     11 import java.util.Collections;
     12 import java.util.List;
     13 import java.util.Map;
     14 import java.util.SortedMap;
     15 import java.util.Stack;
     16 import java.util.TreeMap;
     17 
     18 /**
     19  * Runnable used by the {@link MtpDeviceIndex} to populate its index.
     20  *
     21  * Implementation note: this is the way the index supports a lot of its operations in
     22  * constant time and respecting the need to have bucket names always come before items
     23  * in that bucket when accessing the list sequentially, both in ascending and descending
     24  * orders.
     25  *
     26  * Let's say the data we have in the index is the following:
     27  *  [Bucket A]: [photo 1], [photo 2]
     28  *  [Bucket B]: [photo 3]
     29  *
     30  *  In this case, the lookup index array would be
     31  *  [0, 0, 0, 1, 1]
     32  *
     33  *  Now, whether we access the list in ascending or descending order, we know which bucket
     34  *  to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
     35  *  item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
     36  *  that correspond to indices in this lookup index array, allowing us to calculate the
     37  *  offset of the specific item we want from within a specific bucket.
     38  */
     39 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
     40 public class MtpDeviceIndexRunnable implements Runnable {
     41 
     42   /**
     43    * MtpDeviceIndexRunnable factory.
     44    */
     45   public static class Factory {
     46     public MtpDeviceIndexRunnable createMtpDeviceIndexRunnable(MtpDeviceIndex index) {
     47       return new MtpDeviceIndexRunnable(index);
     48     }
     49   }
     50 
     51   static class Results {
     52     final int[] unifiedLookupIndex;
     53     final IngestObjectInfo[] mtpObjects;
     54     final DateBucket[] buckets;
     55     final DateBucket[] reversedBuckets;
     56 
     57     public Results(
     58         int[] unifiedLookupIndex, IngestObjectInfo[] mtpObjects, DateBucket[] buckets) {
     59       this.unifiedLookupIndex = unifiedLookupIndex;
     60       this.mtpObjects = mtpObjects;
     61       this.buckets = buckets;
     62       this.reversedBuckets = new DateBucket[buckets.length];
     63       for (int i = 0; i < buckets.length; i++) {
     64         this.reversedBuckets[i] = buckets[buckets.length - 1 - i];
     65       }
     66     }
     67   }
     68 
     69   private final MtpDevice mDevice;
     70   protected final MtpDeviceIndex mIndex;
     71   private final long mIndexGeneration;
     72 
     73   private static Factory sDefaultFactory = new Factory();
     74 
     75   public static Factory getFactory() {
     76     return sDefaultFactory;
     77   }
     78 
     79   /**
     80    * Exception thrown when a problem occurred during indexing.
     81    */
     82   @SuppressWarnings("serial")
     83   public class IndexingException extends RuntimeException {}
     84 
     85   MtpDeviceIndexRunnable(MtpDeviceIndex index) {
     86     mIndex = index;
     87     mDevice = index.getDevice();
     88     mIndexGeneration = index.getGeneration();
     89   }
     90 
     91   @Override
     92   public void run() {
     93     try {
     94       indexDevice();
     95     } catch (IndexingException e) {
     96       mIndex.onIndexFinish(false /*successful*/);
     97     }
     98   }
     99 
    100   private void indexDevice() throws IndexingException {
    101     SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp =
    102         new TreeMap<SimpleDate, List<IngestObjectInfo>>();
    103     int numObjects = addAllObjects(bucketsTemp);
    104     mIndex.onSorting();
    105     int numBuckets = bucketsTemp.size();
    106     DateBucket[] buckets = new DateBucket[numBuckets];
    107     IngestObjectInfo[] mtpObjects = new IngestObjectInfo[numObjects];
    108     int[] unifiedLookupIndex = new int[numObjects + numBuckets];
    109     int currentUnifiedIndexEntry = 0;
    110     int currentItemsEntry = 0;
    111     int nextUnifiedEntry, unifiedStartIndex, numBucketObjects, unifiedEndIndex, itemsStartIndex;
    112 
    113     int i = 0;
    114     for (Map.Entry<SimpleDate, List<IngestObjectInfo>> bucketTemp : bucketsTemp.entrySet()) {
    115       List<IngestObjectInfo> objects = bucketTemp.getValue();
    116       Collections.sort(objects);
    117       numBucketObjects = objects.size();
    118 
    119       nextUnifiedEntry = currentUnifiedIndexEntry + numBucketObjects + 1;
    120       Arrays.fill(unifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
    121       unifiedStartIndex = currentUnifiedIndexEntry;
    122       unifiedEndIndex = nextUnifiedEntry - 1;
    123       currentUnifiedIndexEntry = nextUnifiedEntry;
    124 
    125       itemsStartIndex = currentItemsEntry;
    126       for (int j = 0; j < numBucketObjects; j++) {
    127         mtpObjects[currentItemsEntry] = objects.get(j);
    128         currentItemsEntry++;
    129       }
    130       buckets[i] = new DateBucket(bucketTemp.getKey(), unifiedStartIndex, unifiedEndIndex,
    131           itemsStartIndex, numBucketObjects);
    132       i++;
    133     }
    134     if (!mIndex.setIndexingResults(mDevice, mIndexGeneration,
    135         new Results(unifiedLookupIndex, mtpObjects, buckets))) {
    136       throw new IndexingException();
    137     }
    138   }
    139 
    140   private SimpleDate mDateInstance = new SimpleDate();
    141 
    142   protected void addObject(IngestObjectInfo objectInfo,
    143       SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp, int numObjects) {
    144     mDateInstance.setTimestamp(objectInfo.getDateCreated());
    145     List<IngestObjectInfo> bucket = bucketsTemp.get(mDateInstance);
    146     if (bucket == null) {
    147       bucket = new ArrayList<IngestObjectInfo>();
    148       bucketsTemp.put(mDateInstance, bucket);
    149       mDateInstance = new SimpleDate(); // only create new date objects when they are used
    150     }
    151     bucket.add(objectInfo);
    152     mIndex.onObjectIndexed(objectInfo, numObjects);
    153   }
    154 
    155   protected int addAllObjects(SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp)
    156       throws IndexingException {
    157     int numObjects = 0;
    158     for (int storageId : mDevice.getStorageIds()) {
    159       if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
    160         throw new IndexingException();
    161       }
    162       Stack<Integer> pendingDirectories = new Stack<Integer>();
    163       pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
    164       while (!pendingDirectories.isEmpty()) {
    165         if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
    166           throw new IndexingException();
    167         }
    168         int dirHandle = pendingDirectories.pop();
    169         for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
    170           MtpObjectInfo mtpObjectInfo = mDevice.getObjectInfo(objectHandle);
    171           if (mtpObjectInfo == null) {
    172             throw new IndexingException();
    173           }
    174           int format = mtpObjectInfo.getFormat();
    175           if (format == MtpConstants.FORMAT_ASSOCIATION) {
    176             pendingDirectories.add(objectHandle);
    177           } else if (mIndex.isFormatSupported(format)) {
    178             numObjects++;
    179             addObject(new IngestObjectInfo(mtpObjectInfo), bucketsTemp, numObjects);
    180           }
    181         }
    182       }
    183     }
    184     return numObjects;
    185   }
    186 }
    187