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