summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ingest/data/MtpDeviceIndexRunnable.java
blob: 32275898eb253ac8ef1da05b77fdc97139aeb8db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package com.android.gallery3d.ingest.data;

import android.annotation.TargetApi;
import android.mtp.MtpConstants;
import android.mtp.MtpDevice;
import android.mtp.MtpObjectInfo;
import android.os.Build;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;

/**
 * Runnable used by the {@link MtpDeviceIndex} to populate its index.
 *
 * Implementation note: this is the way the index supports a lot of its operations in
 * constant time and respecting the need to have bucket names always come before items
 * in that bucket when accessing the list sequentially, both in ascending and descending
 * orders.
 *
 * Let's say the data we have in the index is the following:
 *  [Bucket A]: [photo 1], [photo 2]
 *  [Bucket B]: [photo 3]
 *
 *  In this case, the lookup index array would be
 *  [0, 0, 0, 1, 1]
 *
 *  Now, whether we access the list in ascending or descending order, we know which bucket
 *  to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
 *  item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
 *  that correspond to indices in this lookup index array, allowing us to calculate the
 *  offset of the specific item we want from within a specific bucket.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class MtpDeviceIndexRunnable implements Runnable {

  /**
   * MtpDeviceIndexRunnable factory.
   */
  public static class Factory {
    public MtpDeviceIndexRunnable createMtpDeviceIndexRunnable(MtpDeviceIndex index) {
      return new MtpDeviceIndexRunnable(index);
    }
  }

  static class Results {
    final int[] unifiedLookupIndex;
    final IngestObjectInfo[] mtpObjects;
    final DateBucket[] buckets;
    final DateBucket[] reversedBuckets;

    public Results(
        int[] unifiedLookupIndex, IngestObjectInfo[] mtpObjects, DateBucket[] buckets) {
      this.unifiedLookupIndex = unifiedLookupIndex;
      this.mtpObjects = mtpObjects;
      this.buckets = buckets;
      this.reversedBuckets = new DateBucket[buckets.length];
      for (int i = 0; i < buckets.length; i++) {
        this.reversedBuckets[i] = buckets[buckets.length - 1 - i];
      }
    }
  }

  private final MtpDevice mDevice;
  protected final MtpDeviceIndex mIndex;
  private final long mIndexGeneration;

  private static Factory sDefaultFactory = new Factory();

  public static Factory getFactory() {
    return sDefaultFactory;
  }

  /**
   * Exception thrown when a problem occurred during indexing.
   */
  @SuppressWarnings("serial")
  public class IndexingException extends RuntimeException {}

  MtpDeviceIndexRunnable(MtpDeviceIndex index) {
    mIndex = index;
    mDevice = index.getDevice();
    mIndexGeneration = index.getGeneration();
  }

  @Override
  public void run() {
    try {
      indexDevice();
    } catch (IndexingException e) {
      mIndex.onIndexFinish(false /*successful*/);
    }
  }

  private void indexDevice() throws IndexingException {
    SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp =
        new TreeMap<SimpleDate, List<IngestObjectInfo>>();
    int numObjects = addAllObjects(bucketsTemp);
    mIndex.onSorting();
    int numBuckets = bucketsTemp.size();
    DateBucket[] buckets = new DateBucket[numBuckets];
    IngestObjectInfo[] mtpObjects = new IngestObjectInfo[numObjects];
    int[] unifiedLookupIndex = new int[numObjects + numBuckets];
    int currentUnifiedIndexEntry = 0;
    int currentItemsEntry = 0;
    int nextUnifiedEntry, unifiedStartIndex, numBucketObjects, unifiedEndIndex, itemsStartIndex;

    int i = 0;
    for (Map.Entry<SimpleDate, List<IngestObjectInfo>> bucketTemp : bucketsTemp.entrySet()) {
      List<IngestObjectInfo> objects = bucketTemp.getValue();
      Collections.sort(objects);
      numBucketObjects = objects.size();

      nextUnifiedEntry = currentUnifiedIndexEntry + numBucketObjects + 1;
      Arrays.fill(unifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
      unifiedStartIndex = currentUnifiedIndexEntry;
      unifiedEndIndex = nextUnifiedEntry - 1;
      currentUnifiedIndexEntry = nextUnifiedEntry;

      itemsStartIndex = currentItemsEntry;
      for (int j = 0; j < numBucketObjects; j++) {
        mtpObjects[currentItemsEntry] = objects.get(j);
        currentItemsEntry++;
      }
      buckets[i] = new DateBucket(bucketTemp.getKey(), unifiedStartIndex, unifiedEndIndex,
          itemsStartIndex, numBucketObjects);
      i++;
    }
    if (!mIndex.setIndexingResults(mDevice, mIndexGeneration,
        new Results(unifiedLookupIndex, mtpObjects, buckets))) {
      throw new IndexingException();
    }
  }

  private SimpleDate mDateInstance = new SimpleDate();

  protected void addObject(IngestObjectInfo objectInfo,
      SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp, int numObjects) {
    mDateInstance.setTimestamp(objectInfo.getDateCreated());
    List<IngestObjectInfo> bucket = bucketsTemp.get(mDateInstance);
    if (bucket == null) {
      bucket = new ArrayList<IngestObjectInfo>();
      bucketsTemp.put(mDateInstance, bucket);
      mDateInstance = new SimpleDate(); // only create new date objects when they are used
    }
    bucket.add(objectInfo);
    mIndex.onObjectIndexed(objectInfo, numObjects);
  }

  protected int addAllObjects(SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp)
      throws IndexingException {
    int numObjects = 0;
    for (int storageId : mDevice.getStorageIds()) {
      if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
        throw new IndexingException();
      }
      Stack<Integer> pendingDirectories = new Stack<Integer>();
      pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
      while (!pendingDirectories.isEmpty()) {
        if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
          throw new IndexingException();
        }
        int dirHandle = pendingDirectories.pop();
        for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
          MtpObjectInfo mtpObjectInfo = mDevice.getObjectInfo(objectHandle);
          if (mtpObjectInfo == null) {
            throw new IndexingException();
          }
          int format = mtpObjectInfo.getFormat();
          if (format == MtpConstants.FORMAT_ASSOCIATION) {
            pendingDirectories.add(objectHandle);
          } else if (mIndex.isFormatSupported(format)) {
            numObjects++;
            addObject(new IngestObjectInfo(mtpObjectInfo), bucketsTemp, numObjects);
          }
        }
      }
    }
    return numObjects;
  }
}