summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ingest/MtpDeviceIndex.java')
-rw-r--r--src/com/android/gallery3d/ingest/MtpDeviceIndex.java596
1 files changed, 0 insertions, 596 deletions
diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
deleted file mode 100644
index d30f94a87..000000000
--- a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ingest;
-
-import android.mtp.MtpConstants;
-import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-/**
- * MTP objects in the index are organized into "buckets," or groupings.
- * At present, these buckets are based on the date an item was created.
- *
- * When the index is created, the buckets are sorted in their natural
- * order, and the items within the buckets sorted by the date they are taken.
- *
- * The index enables the access of items and bucket labels as one unified list.
- * For example, let's say we have the following data in the index:
- * [Bucket A]: [photo 1], [photo 2]
- * [Bucket B]: [photo 3]
- *
- * Then the items can be thought of as being organized as a 5 element list:
- * [Bucket A], [photo 1], [photo 2], [Bucket B], [photo 3]
- *
- * The data can also be accessed in descending order, in which case the list
- * would be a bit different from simply reversing the ascending list, since the
- * bucket labels need to always be at the beginning:
- * [Bucket B], [photo 3], [Bucket A], [photo 2], [photo 1]
- *
- * The index enables all the following operations in constant time, both for
- * ascending and descending views of the data:
- * - get/getAscending/getDescending: get an item at a specified list position
- * - size: get the total number of items (bucket labels and MTP objects)
- * - getFirstPositionForBucketNumber
- * - getBucketNumberForPosition
- * - isFirstInBucket
- *
- * See the comments in buildLookupIndex for implementation notes.
- */
-public class MtpDeviceIndex {
-
- public static final int FORMAT_MOV = 0x300D; // For some reason this is not in MtpConstants
-
- public static final Set<Integer> SUPPORTED_IMAGE_FORMATS;
- public static final Set<Integer> SUPPORTED_VIDEO_FORMATS;
-
- static {
- SUPPORTED_IMAGE_FORMATS = new HashSet<Integer>();
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_JFIF);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_EXIF_JPEG);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_PNG);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_GIF);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_BMP);
-
- SUPPORTED_VIDEO_FORMATS = new HashSet<Integer>();
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_3GP_CONTAINER);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_AVI);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MP4_CONTAINER);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MPEG);
- // TODO: add FORMAT_MOV once Media Scanner supports .mov files
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDevice == null) ? 0 : mDevice.getDeviceId());
- result = prime * result + mGeneration;
- return result;
- }
-
- public interface ProgressListener {
- public void onObjectIndexed(MtpObjectInfo object, int numVisited);
-
- public void onSorting();
-
- public void onIndexFinish();
- }
-
- public enum SortOrder {
- Ascending, Descending
- }
-
- private MtpDevice mDevice;
- private int[] mUnifiedLookupIndex;
- private MtpObjectInfo[] mMtpObjects;
- private DateBucket[] mBuckets;
- private int mGeneration = 0;
-
- public enum Progress {
- Uninitialized, Initialized, Pending, Started, Sorting, Finished
- }
-
- private Progress mProgress = Progress.Uninitialized;
- private ProgressListener mProgressListener;
-
- private static final MtpDeviceIndex sInstance = new MtpDeviceIndex();
- private static final MtpObjectTimestampComparator sMtpObjectComparator =
- new MtpObjectTimestampComparator();
-
- public static MtpDeviceIndex getInstance() {
- return sInstance;
- }
-
- private MtpDeviceIndex() {
- }
-
- synchronized public MtpDevice getDevice() {
- return mDevice;
- }
-
- /**
- * Sets the MtpDevice that should be indexed and initializes state, but does
- * not kick off the actual indexing task, which is instead done by using
- * {@link #getIndexRunnable()}
- *
- * @param device The MtpDevice that should be indexed
- */
- synchronized public void setDevice(MtpDevice device) {
- if (device == mDevice) return;
- mDevice = device;
- resetState();
- }
-
- /**
- * Provides a Runnable for the indexing task assuming the state has already
- * been correctly initialized (by calling {@link #setDevice(MtpDevice)}) and
- * has not already been run.
- *
- * @return Runnable for the main indexing task
- */
- synchronized public Runnable getIndexRunnable() {
- if (mProgress != Progress.Initialized) return null;
- mProgress = Progress.Pending;
- return new IndexRunnable(mDevice);
- }
-
- synchronized public boolean indexReady() {
- return mProgress == Progress.Finished;
- }
-
- synchronized public Progress getProgress() {
- return mProgress;
- }
-
- /**
- * @param listener Listener to change to
- * @return Progress at the time the listener was added (useful for
- * configuring initial UI state)
- */
- synchronized public Progress setProgressListener(ProgressListener listener) {
- mProgressListener = listener;
- return mProgress;
- }
-
- /**
- * Make the listener null if it matches the argument
- *
- * @param listener Listener to unset, if currently registered
- */
- synchronized public void unsetProgressListener(ProgressListener listener) {
- if (mProgressListener == listener)
- mProgressListener = null;
- }
-
- /**
- * @return The total number of elements in the index (labels and items)
- */
- public int size() {
- return mProgress == Progress.Finished ? mUnifiedLookupIndex.length : 0;
- }
-
- /**
- * @param position Index of item to fetch, where 0 is the first item in the
- * specified order
- * @param order
- * @return the bucket label or MtpObjectInfo at the specified position and
- * order
- */
- public Object get(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return null;
- if(order == SortOrder.Ascending) {
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
- if (bucket.unifiedStartIndex == position) {
- return bucket.bucket;
- } else {
- return mMtpObjects[bucket.itemsStartIndex + position - 1
- - bucket.unifiedStartIndex];
- }
- } else {
- int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
- if (bucket.unifiedEndIndex == zeroIndex) {
- return bucket.bucket;
- } else {
- return mMtpObjects[bucket.itemsStartIndex + zeroIndex
- - bucket.unifiedStartIndex];
- }
- }
- }
-
- /**
- * @param position Index of item to fetch from a view of the data that doesn't
- * include labels and is in the specified order
- * @return position-th item in specified order, when not including labels
- */
- public MtpObjectInfo getWithoutLabels(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return null;
- if (order == SortOrder.Ascending) {
- return mMtpObjects[position];
- } else {
- return mMtpObjects[mMtpObjects.length - 1 - position];
- }
- }
-
- /**
- * Although this is O(log(number of buckets)), and thus should not be used
- * in hotspots, even if the attached device has items for every day for
- * a five-year timeframe, it would still only take 11 iterations at most,
- * so shouldn't be a huge issue.
- * @param position Index of item to map from a view of the data that doesn't
- * include labels and is in the specified order
- * @param order
- * @return position in a view of the data that does include labels
- */
- public int getPositionFromPositionWithoutLabels(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return -1;
- if (order == SortOrder.Descending) {
- position = mMtpObjects.length - 1 - position;
- }
- int bucketNumber = 0;
- int iMin = 0;
- int iMax = mBuckets.length - 1;
- while (iMax >= iMin) {
- int iMid = (iMax + iMin) / 2;
- if (mBuckets[iMid].itemsStartIndex + mBuckets[iMid].numItems <= position) {
- iMin = iMid + 1;
- } else if (mBuckets[iMid].itemsStartIndex > position) {
- iMax = iMid - 1;
- } else {
- bucketNumber = iMid;
- break;
- }
- }
- int mappedPos = mBuckets[bucketNumber].unifiedStartIndex
- + position - mBuckets[bucketNumber].itemsStartIndex;
- if (order == SortOrder.Descending) {
- mappedPos = mUnifiedLookupIndex.length - 1 - mappedPos;
- }
- return mappedPos;
- }
-
- public int getPositionWithoutLabelsFromPosition(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return -1;
- if(order == SortOrder.Ascending) {
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
- if (bucket.unifiedStartIndex == position) position++;
- return bucket.itemsStartIndex + position - 1 - bucket.unifiedStartIndex;
- } else {
- int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
- if (bucket.unifiedEndIndex == zeroIndex) zeroIndex--;
- return mMtpObjects.length - 1 - bucket.itemsStartIndex
- - zeroIndex + bucket.unifiedStartIndex;
- }
- }
-
- /**
- * @return The number of MTP items in the index (without labels)
- */
- public int sizeWithoutLabels() {
- return mProgress == Progress.Finished ? mMtpObjects.length : 0;
- }
-
- public int getFirstPositionForBucketNumber(int bucketNumber, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mBuckets[bucketNumber].unifiedStartIndex;
- } else {
- return mUnifiedLookupIndex.length - mBuckets[mBuckets.length - 1 - bucketNumber].unifiedEndIndex - 1;
- }
- }
-
- public int getBucketNumberForPosition(int position, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mUnifiedLookupIndex[position];
- } else {
- return mBuckets.length - 1 - mUnifiedLookupIndex[mUnifiedLookupIndex.length - 1 - position];
- }
- }
-
- public boolean isFirstInBucket(int position, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mBuckets[mUnifiedLookupIndex[position]].unifiedStartIndex == position;
- } else {
- position = mUnifiedLookupIndex.length - 1 - position;
- return mBuckets[mUnifiedLookupIndex[position]].unifiedEndIndex == position;
- }
- }
-
- private Object[] mCachedReverseBuckets;
-
- public Object[] getBuckets(SortOrder order) {
- if (mBuckets == null) return null;
- if (order == SortOrder.Ascending) {
- return mBuckets;
- } else {
- if (mCachedReverseBuckets == null) {
- computeReversedBuckets();
- }
- return mCachedReverseBuckets;
- }
- }
-
- /*
- * See the comments for buildLookupIndex for notes on the specific fields of
- * this class.
- */
- private class DateBucket implements Comparable<DateBucket> {
- SimpleDate bucket;
- List<MtpObjectInfo> tempElementsList = new ArrayList<MtpObjectInfo>();
- int unifiedStartIndex;
- int unifiedEndIndex;
- int itemsStartIndex;
- int numItems;
-
- public DateBucket(SimpleDate bucket) {
- this.bucket = bucket;
- }
-
- public DateBucket(SimpleDate bucket, MtpObjectInfo firstElement) {
- this(bucket);
- tempElementsList.add(firstElement);
- }
-
- void sortElements(Comparator<MtpObjectInfo> comparator) {
- Collections.sort(tempElementsList, comparator);
- }
-
- @Override
- public String toString() {
- return bucket.toString();
- }
-
- @Override
- public int hashCode() {
- return bucket.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof DateBucket)) return false;
- DateBucket other = (DateBucket) obj;
- if (bucket == null) {
- if (other.bucket != null) return false;
- } else if (!bucket.equals(other.bucket)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int compareTo(DateBucket another) {
- return this.bucket.compareTo(another.bucket);
- }
- }
-
- /**
- * Comparator to sort MtpObjectInfo objects by date created.
- */
- private static class MtpObjectTimestampComparator implements Comparator<MtpObjectInfo> {
- @Override
- public int compare(MtpObjectInfo o1, MtpObjectInfo o2) {
- long diff = o1.getDateCreated() - o2.getDateCreated();
- if (diff < 0) {
- return -1;
- } else if (diff == 0) {
- return 0;
- } else {
- return 1;
- }
- }
- }
-
- private void resetState() {
- mGeneration++;
- mUnifiedLookupIndex = null;
- mMtpObjects = null;
- mBuckets = null;
- mCachedReverseBuckets = null;
- mProgress = (mDevice == null) ? Progress.Uninitialized : Progress.Initialized;
- }
-
-
- private class IndexRunnable implements Runnable {
- private int[] mUnifiedLookupIndex;
- private MtpObjectInfo[] mMtpObjects;
- private DateBucket[] mBuckets;
- private Map<SimpleDate, DateBucket> mBucketsTemp;
- private MtpDevice mDevice;
- private int mNumObjects = 0;
-
- private class IndexingException extends Exception {};
-
- public IndexRunnable(MtpDevice device) {
- mDevice = device;
- }
-
- /*
- * 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.
- */
- private void buildLookupIndex() {
- int numBuckets = mBuckets.length;
- mUnifiedLookupIndex = new int[mNumObjects + numBuckets];
- int currentUnifiedIndexEntry = 0;
- int nextUnifiedEntry;
-
- mMtpObjects = new MtpObjectInfo[mNumObjects];
- int currentItemsEntry = 0;
- for (int i = 0; i < numBuckets; i++) {
- DateBucket bucket = mBuckets[i];
- nextUnifiedEntry = currentUnifiedIndexEntry + bucket.tempElementsList.size() + 1;
- Arrays.fill(mUnifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
- bucket.unifiedStartIndex = currentUnifiedIndexEntry;
- bucket.unifiedEndIndex = nextUnifiedEntry - 1;
- currentUnifiedIndexEntry = nextUnifiedEntry;
-
- bucket.itemsStartIndex = currentItemsEntry;
- bucket.numItems = bucket.tempElementsList.size();
- for (int j = 0; j < bucket.numItems; j++) {
- mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j);
- currentItemsEntry++;
- }
- bucket.tempElementsList = null;
- }
- }
-
- private void copyResults() {
- MtpDeviceIndex.this.mUnifiedLookupIndex = mUnifiedLookupIndex;
- MtpDeviceIndex.this.mMtpObjects = mMtpObjects;
- MtpDeviceIndex.this.mBuckets = mBuckets;
- mUnifiedLookupIndex = null;
- mMtpObjects = null;
- mBuckets = null;
- }
-
- @Override
- public void run() {
- try {
- indexDevice();
- } catch (IndexingException e) {
- synchronized (MtpDeviceIndex.this) {
- resetState();
- if (mProgressListener != null) {
- mProgressListener.onIndexFinish();
- }
- }
- }
- }
-
- private void indexDevice() throws IndexingException {
- synchronized (MtpDeviceIndex.this) {
- mProgress = Progress.Started;
- }
- mBucketsTemp = new HashMap<SimpleDate, DateBucket>();
- for (int storageId : mDevice.getStorageIds()) {
- if (mDevice != getDevice()) throw new IndexingException();
- Stack<Integer> pendingDirectories = new Stack<Integer>();
- pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
- while (!pendingDirectories.isEmpty()) {
- if (mDevice != getDevice()) throw new IndexingException();
- int dirHandle = pendingDirectories.pop();
- for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
- MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle);
- if (objectInfo == null) throw new IndexingException();
- int format = objectInfo.getFormat();
- if (format == MtpConstants.FORMAT_ASSOCIATION) {
- pendingDirectories.add(objectHandle);
- } else if (SUPPORTED_IMAGE_FORMATS.contains(format)
- || SUPPORTED_VIDEO_FORMATS.contains(format)) {
- addObject(objectInfo);
- }
- }
- }
- }
- Collection<DateBucket> values = mBucketsTemp.values();
- mBucketsTemp = null;
- mBuckets = values.toArray(new DateBucket[values.size()]);
- values = null;
- synchronized (MtpDeviceIndex.this) {
- mProgress = Progress.Sorting;
- if (mProgressListener != null) {
- mProgressListener.onSorting();
- }
- }
- sortAll();
- buildLookupIndex();
- synchronized (MtpDeviceIndex.this) {
- if (mDevice != getDevice()) throw new IndexingException();
- copyResults();
-
- /*
- * In order for getBuckets to operate in constant time for descending
- * order, we must precompute a reversed array of the buckets, mainly
- * because the android.widget.SectionIndexer interface which adapters
- * that call getBuckets implement depends on section numbers to be
- * ascending relative to the scroll position, so we must have this for
- * descending order or the scrollbar goes crazy.
- */
- computeReversedBuckets();
-
- mProgress = Progress.Finished;
- if (mProgressListener != null) {
- mProgressListener.onIndexFinish();
- }
- }
- }
-
- private SimpleDate mDateInstance = new SimpleDate();
-
- private void addObject(MtpObjectInfo objectInfo) {
- mNumObjects++;
- mDateInstance.setTimestamp(objectInfo.getDateCreated());
- DateBucket bucket = mBucketsTemp.get(mDateInstance);
- if (bucket == null) {
- bucket = new DateBucket(mDateInstance, objectInfo);
- mBucketsTemp.put(mDateInstance, bucket);
- mDateInstance = new SimpleDate(); // only create new date
- // objects when they are used
- return;
- } else {
- bucket.tempElementsList.add(objectInfo);
- }
- if (mProgressListener != null) {
- mProgressListener.onObjectIndexed(objectInfo, mNumObjects);
- }
- }
-
- private void sortAll() {
- Arrays.sort(mBuckets);
- for (DateBucket bucket : mBuckets) {
- bucket.sortElements(sMtpObjectComparator);
- }
- }
-
- }
-
- private void computeReversedBuckets() {
- mCachedReverseBuckets = new Object[mBuckets.length];
- for (int i = 0; i < mCachedReverseBuckets.length; i++) {
- mCachedReverseBuckets[i] = mBuckets[mBuckets.length - 1 - i];
- }
- }
-}