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 {
createMtpDeviceIndexRunnable(MtpDeviceIndex index)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 
Results( int[] unifiedLookupIndex, IngestObjectInfo[] mtpObjects, DateBucket[] buckets)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 
getFactory()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 
MtpDeviceIndexRunnable(MtpDeviceIndex index)85   MtpDeviceIndexRunnable(MtpDeviceIndex index) {
86     mIndex = index;
87     mDevice = index.getDevice();
88     mIndexGeneration = index.getGeneration();
89   }
90 
91   @Override
run()92   public void run() {
93     try {
94       indexDevice();
95     } catch (IndexingException e) {
96       mIndex.onIndexFinish(false /*successful*/);
97     }
98   }
99 
indexDevice()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 
addObject(IngestObjectInfo objectInfo, SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp, int numObjects)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 
addAllObjects(SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp)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           if (mtpObjectInfo.getFormat() == MtpConstants.FORMAT_ASSOCIATION) {
175             pendingDirectories.add(objectHandle);
176           } else if (mIndex.isFormatSupported(mtpObjectInfo)) {
177             numObjects++;
178             addObject(new IngestObjectInfo(mtpObjectInfo), bucketsTemp, numObjects);
179           }
180         }
181       }
182     }
183     return numObjects;
184   }
185 }
186