1 package com.android.gallery3d.ingest.data;
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;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.List;
14 import java.util.SortedMap;
15 import java.util.Stack;
16 import java.util.TreeMap;
19 * Runnable used by the {@link MtpDeviceIndex} to populate its index.
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
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]
30 * In this case, the lookup index array would be
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.
39 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
40 public class MtpDeviceIndexRunnable implements Runnable {
43 * MtpDeviceIndexRunnable factory.
45 public static class Factory {
46 public MtpDeviceIndexRunnable createMtpDeviceIndexRunnable(MtpDeviceIndex index) {
47 return new MtpDeviceIndexRunnable(index);
51 static class Results {
52 final int[] unifiedLookupIndex;
53 final IngestObjectInfo[] mtpObjects;
54 final DateBucket[] buckets;
55 final DateBucket[] reversedBuckets;
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];
69 private final MtpDevice mDevice;
70 protected final MtpDeviceIndex mIndex;
71 private final long mIndexGeneration;
73 private static Factory sDefaultFactory = new Factory();
75 public static Factory getFactory() {
76 return sDefaultFactory;
80 * Exception thrown when a problem occurred during indexing.
82 @SuppressWarnings("serial")
83 public class IndexingException extends RuntimeException {}
85 MtpDeviceIndexRunnable(MtpDeviceIndex index) {
87 mDevice = index.getDevice();
88 mIndexGeneration = index.getGeneration();
95 } catch (IndexingException e) {
96 mIndex.onIndexFinish(false /*successful*/);
100 private void indexDevice() throws IndexingException {
101 SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp =
102 new TreeMap<SimpleDate, List<IngestObjectInfo>>();
103 int numObjects = addAllObjects(bucketsTemp);
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;
114 for (Map.Entry<SimpleDate, List<IngestObjectInfo>> bucketTemp : bucketsTemp.entrySet()) {
115 List<IngestObjectInfo> objects = bucketTemp.getValue();
116 Collections.sort(objects);
117 numBucketObjects = objects.size();
119 nextUnifiedEntry = currentUnifiedIndexEntry + numBucketObjects + 1;
120 Arrays.fill(unifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
121 unifiedStartIndex = currentUnifiedIndexEntry;
122 unifiedEndIndex = nextUnifiedEntry - 1;
123 currentUnifiedIndexEntry = nextUnifiedEntry;
125 itemsStartIndex = currentItemsEntry;
126 for (int j = 0; j < numBucketObjects; j++) {
127 mtpObjects[currentItemsEntry] = objects.get(j);
130 buckets[i] = new DateBucket(bucketTemp.getKey(), unifiedStartIndex, unifiedEndIndex,
131 itemsStartIndex, numBucketObjects);
134 if (!mIndex.setIndexingResults(mDevice, mIndexGeneration,
135 new Results(unifiedLookupIndex, mtpObjects, buckets))) {
136 throw new IndexingException();
140 private SimpleDate mDateInstance = new SimpleDate();
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
151 bucket.add(objectInfo);
152 mIndex.onObjectIndexed(objectInfo, numObjects);
155 protected int addAllObjects(SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp)
156 throws IndexingException {
158 for (int storageId : mDevice.getStorageIds()) {
159 if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
160 throw new IndexingException();
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();
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();
174 int format = mtpObjectInfo.getFormat();
175 if (format == MtpConstants.FORMAT_ASSOCIATION) {
176 pendingDirectories.add(objectHandle);
177 } else if (mIndex.isFormatSupported(format)) {
179 addObject(new IngestObjectInfo(mtpObjectInfo), bucketsTemp, numObjects);