1d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu/*
2d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * Copyright (C) 2013 The Android Open Source Project
3d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
4d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * Licensed under the Apache License, Version 2.0 (the "License");
5d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * you may not use this file except in compliance with the License.
6d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * You may obtain a copy of the License at
7d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
8d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *      http://www.apache.org/licenses/LICENSE-2.0
9d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
10d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * Unless required by applicable law or agreed to in writing, software
11d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * distributed under the License is distributed on an "AS IS" BASIS,
12d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * See the License for the specific language governing permissions and
14d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * limitations under the License.
15d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu */
16d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
17d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescupackage com.android.gallery3d.ingest;
18d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
19d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport android.mtp.MtpConstants;
20d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport android.mtp.MtpDevice;
21d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport android.mtp.MtpObjectInfo;
22d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
23d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.ArrayList;
24d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Arrays;
25d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Collection;
26d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Collections;
27d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Comparator;
28d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.HashMap;
29baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescuimport java.util.HashSet;
30d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.List;
31d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Map;
32baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescuimport java.util.Set;
33d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescuimport java.util.Stack;
34d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
35d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu/**
36d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * MTP objects in the index are organized into "buckets," or groupings.
37d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * At present, these buckets are based on the date an item was created.
38d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
39d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * When the index is created, the buckets are sorted in their natural
40d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * order, and the items within the buckets sorted by the date they are taken.
41d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
42d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * The index enables the access of items and bucket labels as one unified list.
43d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * For example, let's say we have the following data in the index:
44d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *    [Bucket A]: [photo 1], [photo 2]
45d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *    [Bucket B]: [photo 3]
46d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
47d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * Then the items can be thought of as being organized as a 5 element list:
48d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   [Bucket A], [photo 1], [photo 2], [Bucket B], [photo 3]
49d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
50d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * The data can also be accessed in descending order, in which case the list
51d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * would be a bit different from simply reversing the ascending list, since the
52d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * bucket labels need to always be at the beginning:
53d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   [Bucket B], [photo 3], [Bucket A], [photo 2], [photo 1]
54d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
55d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * The index enables all the following operations in constant time, both for
56d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * ascending and descending views of the data:
57d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   - get/getAscending/getDescending: get an item at a specified list position
58d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   - size: get the total number of items (bucket labels and MTP objects)
59d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   - getFirstPositionForBucketNumber
60d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   - getBucketNumberForPosition
61d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *   - isFirstInBucket
62d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu *
63d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu * See the comments in buildLookupIndex for implementation notes.
64d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu */
65d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescupublic class MtpDeviceIndex {
66d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
67baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu    public static final int FORMAT_MOV = 0x300D; // For some reason this is not in MtpConstants
68baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu
69baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu    public static final Set<Integer> SUPPORTED_IMAGE_FORMATS;
70baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu    public static final Set<Integer> SUPPORTED_VIDEO_FORMATS;
71baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu
72baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu    static {
73baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS = new HashSet<Integer>();
74baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_JFIF);
75baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_EXIF_JPEG);
76baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_PNG);
77baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_GIF);
78baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_BMP);
79baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu
80baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_VIDEO_FORMATS = new HashSet<Integer>();
81baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_3GP_CONTAINER);
82baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_AVI);
83baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MP4_CONTAINER);
84baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MPEG);
85baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu        // TODO: add FORMAT_MOV once Media Scanner supports .mov files
86baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu    }
87baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu
88d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    @Override
89d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public int hashCode() {
90d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        final int prime = 31;
91d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        int result = 1;
92d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        result = prime * result + ((mDevice == null) ? 0 : mDevice.getDeviceId());
93d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        result = prime * result + mGeneration;
94d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return result;
95d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
96d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
97d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public interface ProgressListener {
98d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public void onObjectIndexed(MtpObjectInfo object, int numVisited);
99d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
100d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public void onSorting();
101d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
102d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public void onIndexFinish();
103d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
104d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
105d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public enum SortOrder {
106d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        Ascending, Descending
107d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
108d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
109d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private MtpDevice mDevice;
110d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private int[] mUnifiedLookupIndex;
111d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private MtpObjectInfo[] mMtpObjects;
112d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private DateBucket[] mBuckets;
113d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private int mGeneration = 0;
114d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
115d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public enum Progress {
116d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        Uninitialized, Initialized, Pending, Started, Sorting, Finished
117d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
118d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
119d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private Progress mProgress = Progress.Uninitialized;
120d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private ProgressListener mProgressListener;
121d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
122d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private static final MtpDeviceIndex sInstance = new MtpDeviceIndex();
123eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu    private static final MtpObjectTimestampComparator sMtpObjectComparator =
124eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            new MtpObjectTimestampComparator();
125d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
126d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public static MtpDeviceIndex getInstance() {
127d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return sInstance;
128d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
129d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
130d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private MtpDeviceIndex() {
131d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
132d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
133d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public MtpDevice getDevice() {
134d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return mDevice;
135d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
136d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
137d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
138d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * Sets the MtpDevice that should be indexed and initializes state, but does
139d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * not kick off the actual indexing task, which is instead done by using
140d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * {@link #getIndexRunnable()}
141d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *
142d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @param device The MtpDevice that should be indexed
143d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
144d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public void setDevice(MtpDevice device) {
145d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (device == mDevice) return;
146d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mDevice = device;
147d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        resetState();
148d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
149d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
150d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
151d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * Provides a Runnable for the indexing task assuming the state has already
152d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * been correctly initialized (by calling {@link #setDevice(MtpDevice)}) and
153d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * has not already been run.
154d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *
155d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @return Runnable for the main indexing task
156d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
157d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public Runnable getIndexRunnable() {
158d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (mProgress != Progress.Initialized) return null;
159d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mProgress = Progress.Pending;
160eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        return new IndexRunnable(mDevice);
161d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
162d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
1636516d271e11f9ae9928be3bb70c99531a842c577Bobby Georgescu    synchronized public boolean indexReady() {
1646516d271e11f9ae9928be3bb70c99531a842c577Bobby Georgescu        return mProgress == Progress.Finished;
1656516d271e11f9ae9928be3bb70c99531a842c577Bobby Georgescu    }
1666516d271e11f9ae9928be3bb70c99531a842c577Bobby Georgescu
167d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public Progress getProgress() {
168d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return mProgress;
169d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
170d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
171d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
172d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @param listener Listener to change to
173d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @return Progress at the time the listener was added (useful for
174d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *         configuring initial UI state)
175d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
176d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public Progress setProgressListener(ProgressListener listener) {
177d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mProgressListener = listener;
178d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return mProgress;
179d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
180d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
181d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
182d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * Make the listener null if it matches the argument
183d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *
184d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @param listener Listener to unset, if currently registered
185d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
186d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    synchronized public void unsetProgressListener(ProgressListener listener) {
187d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (mProgressListener == listener)
188d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            mProgressListener = null;
189d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
190d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
191d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
192d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @return The total number of elements in the index (labels and items)
193d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
194d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public int size() {
195d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return mProgress == Progress.Finished ? mUnifiedLookupIndex.length : 0;
196d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
197d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
198d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
199d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @param position Index of item to fetch, where 0 is the first item in the
200d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *            specified order
201d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @param order
202d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @return the bucket label or MtpObjectInfo at the specified position and
203d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     *         order
204d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
205d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public Object get(int position, SortOrder order) {
206c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (mProgress != Progress.Finished) return null;
207d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if(order == SortOrder.Ascending) {
208c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
209c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            if (bucket.unifiedStartIndex == position) {
210c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                return bucket.bucket;
211c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            } else {
212c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                return mMtpObjects[bucket.itemsStartIndex + position - 1
213c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                                   - bucket.unifiedStartIndex];
214c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            }
215d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
216c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
217c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
218c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            if (bucket.unifiedEndIndex == zeroIndex) {
219c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                return bucket.bucket;
220c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            } else {
221c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                return mMtpObjects[bucket.itemsStartIndex + zeroIndex
222c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                                   - bucket.unifiedStartIndex];
223c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            }
224d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
225d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
226d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
227d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
228c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * @param position Index of item to fetch from a view of the data that doesn't
229c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     *            include labels and is in the specified order
230c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * @return position-th item in specified order, when not including labels
231d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
232c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu    public MtpObjectInfo getWithoutLabels(int position, SortOrder order) {
233d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (mProgress != Progress.Finished) return null;
234c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (order == SortOrder.Ascending) {
235c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            return mMtpObjects[position];
236d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
237c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            return mMtpObjects[mMtpObjects.length - 1 - position];
238d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
239d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
240d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
241d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
242c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * Although this is O(log(number of buckets)), and thus should not be used
243c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * in hotspots, even if the attached device has items for every day for
244c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * a five-year timeframe, it would still only take 11 iterations at most,
245c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * so shouldn't be a huge issue.
246c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * @param position Index of item to map from a view of the data that doesn't
247c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     *            include labels and is in the specified order
248c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * @param order
249c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu     * @return position in a view of the data that does include labels
250d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
251c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu    public int getPositionFromPositionWithoutLabels(int position, SortOrder order) {
252c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (mProgress != Progress.Finished) return -1;
253c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (order == SortOrder.Descending) {
254c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            position = mMtpObjects.length - 1 - position;
255c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        }
256c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        int bucketNumber = 0;
257c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        int iMin = 0;
258c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        int iMax = mBuckets.length - 1;
259c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        while (iMax >= iMin) {
260c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            int iMid = (iMax + iMin) / 2;
261c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            if (mBuckets[iMid].itemsStartIndex + mBuckets[iMid].numItems <= position) {
262c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                iMin = iMid + 1;
263c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            } else if (mBuckets[iMid].itemsStartIndex > position) {
264c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                iMax = iMid - 1;
265c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            } else {
266c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                bucketNumber = iMid;
267c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                break;
268c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            }
269d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
27096e6ddb24dd149aa991ef007a394ea9592066d14John Hoford        if (mBuckets.length == 0 || mUnifiedLookupIndex.length == 0) {
27196e6ddb24dd149aa991ef007a394ea9592066d14John Hoford            return -1;
27296e6ddb24dd149aa991ef007a394ea9592066d14John Hoford        }
273c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        int mappedPos = mBuckets[bucketNumber].unifiedStartIndex
274c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                + position - mBuckets[bucketNumber].itemsStartIndex;
275c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (order == SortOrder.Descending) {
276c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            mappedPos = mUnifiedLookupIndex.length - 1 - mappedPos;
277c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        }
278c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        return mappedPos;
279d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
280d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
281c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu    public int getPositionWithoutLabelsFromPosition(int position, SortOrder order) {
282c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if (mProgress != Progress.Finished) return -1;
283c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        if(order == SortOrder.Ascending) {
284c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
285c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            if (bucket.unifiedStartIndex == position) position++;
286c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            return bucket.itemsStartIndex + position - 1 - bucket.unifiedStartIndex;
287d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
288c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
28996e6ddb24dd149aa991ef007a394ea9592066d14John Hoford            if (mBuckets.length == 0 || mUnifiedLookupIndex.length == 0) {
29096e6ddb24dd149aa991ef007a394ea9592066d14John Hoford                return -1;
29196e6ddb24dd149aa991ef007a394ea9592066d14John Hoford            }
292c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
293c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            if (bucket.unifiedEndIndex == zeroIndex) zeroIndex--;
294c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu            return mMtpObjects.length - 1 - bucket.itemsStartIndex
295c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                    - zeroIndex + bucket.unifiedStartIndex;
296d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
297d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
298d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
299d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
300d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * @return The number of MTP items in the index (without labels)
301d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
302d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public int sizeWithoutLabels() {
303d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        return mProgress == Progress.Finished ? mMtpObjects.length : 0;
304d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
305d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
306d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public int getFirstPositionForBucketNumber(int bucketNumber, SortOrder order) {
307d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (order == SortOrder.Ascending) {
308d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mBuckets[bucketNumber].unifiedStartIndex;
309d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
310d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mUnifiedLookupIndex.length - mBuckets[mBuckets.length - 1 - bucketNumber].unifiedEndIndex - 1;
311d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
312d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
313d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
314d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public int getBucketNumberForPosition(int position, SortOrder order) {
315d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (order == SortOrder.Ascending) {
316d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mUnifiedLookupIndex[position];
317d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
318d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mBuckets.length - 1 - mUnifiedLookupIndex[mUnifiedLookupIndex.length - 1 - position];
319d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
320d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
321d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
322d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public boolean isFirstInBucket(int position, SortOrder order) {
323d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (order == SortOrder.Ascending) {
324d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mBuckets[mUnifiedLookupIndex[position]].unifiedStartIndex == position;
325d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
326d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            position = mUnifiedLookupIndex.length - 1 - position;
327d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mBuckets[mUnifiedLookupIndex[position]].unifiedEndIndex == position;
328d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
329d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
330d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
331d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private Object[] mCachedReverseBuckets;
332d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
333d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    public Object[] getBuckets(SortOrder order) {
334d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (mBuckets == null) return null;
335d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        if (order == SortOrder.Ascending) {
336d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mBuckets;
337d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        } else {
338d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (mCachedReverseBuckets == null) {
339d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                computeReversedBuckets();
340d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
341d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return mCachedReverseBuckets;
342d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
343d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
344d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
345d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /*
346d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * See the comments for buildLookupIndex for notes on the specific fields of
347d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * this class.
348d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
349d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private class DateBucket implements Comparable<DateBucket> {
350d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        SimpleDate bucket;
351d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        List<MtpObjectInfo> tempElementsList = new ArrayList<MtpObjectInfo>();
352d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        int unifiedStartIndex;
353d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        int unifiedEndIndex;
354d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        int itemsStartIndex;
355c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu        int numItems;
356d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
357d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public DateBucket(SimpleDate bucket) {
358d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            this.bucket = bucket;
359d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
360d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
361d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public DateBucket(SimpleDate bucket, MtpObjectInfo firstElement) {
362d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            this(bucket);
363d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            tempElementsList.add(firstElement);
364d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
365d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
366d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        void sortElements(Comparator<MtpObjectInfo> comparator) {
367d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            Collections.sort(tempElementsList, comparator);
368d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
369d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
370d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        @Override
371d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public String toString() {
372d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return bucket.toString();
373d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
374d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
375d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        @Override
376d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public int hashCode() {
377d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return bucket.hashCode();
378d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
379d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
380d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        @Override
381d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public boolean equals(Object obj) {
382d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (this == obj) return true;
383d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (obj == null) return false;
384d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (!(obj instanceof DateBucket)) return false;
385d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            DateBucket other = (DateBucket) obj;
386d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (bucket == null) {
387d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                if (other.bucket != null) return false;
388d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            } else if (!bucket.equals(other.bucket)) {
389d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                return false;
390d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
391d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return true;
392d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
393d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
394d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        @Override
395d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public int compareTo(DateBucket another) {
396d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            return this.bucket.compareTo(another.bucket);
397d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
398d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
399d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
400d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    /**
401d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     * Comparator to sort MtpObjectInfo objects by date created.
402d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu     */
403d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private static class MtpObjectTimestampComparator implements Comparator<MtpObjectInfo> {
404d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        @Override
405d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        public int compare(MtpObjectInfo o1, MtpObjectInfo o2) {
406d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            long diff = o1.getDateCreated() - o2.getDateCreated();
407d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            if (diff < 0) {
408d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                return -1;
409d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            } else if (diff == 0) {
410d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                return 0;
411d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            } else {
412d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                return 1;
413d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
414d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
415d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
416d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
417d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private void resetState() {
418d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mGeneration++;
419d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mUnifiedLookupIndex = null;
420d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mMtpObjects = null;
421d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mBuckets = null;
422d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mCachedReverseBuckets = null;
423d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mProgress = (mDevice == null) ? Progress.Uninitialized : Progress.Initialized;
424d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
425d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
426eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
427eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu    private class IndexRunnable implements Runnable {
428eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private int[] mUnifiedLookupIndex;
429eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private MtpObjectInfo[] mMtpObjects;
430eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private DateBucket[] mBuckets;
431eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private Map<SimpleDate, DateBucket> mBucketsTemp;
432eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private MtpDevice mDevice;
433eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private int mNumObjects = 0;
434eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
435eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private class IndexingException extends Exception {};
436eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
437eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        public IndexRunnable(MtpDevice device) {
438eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mDevice = device;
439eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        }
440eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
441eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        /*
442eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         * Implementation note: this is the way the index supports a lot of its operations in
443eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         * constant time and respecting the need to have bucket names always come before items
444eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         * in that bucket when accessing the list sequentially, both in ascending and descending
445eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         * orders.
446eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *
447eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         * Let's say the data we have in the index is the following:
448eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  [Bucket A]: [photo 1], [photo 2]
449eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  [Bucket B]: [photo 3]
450eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *
451eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  In this case, the lookup index array would be
452eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  [0, 0, 0, 1, 1]
453eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *
454eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  Now, whether we access the list in ascending or descending order, we know which bucket
455eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
456eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
457eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  that correspond to indices in this lookup index array, allowing us to calculate the
458eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         *  offset of the specific item we want from within a specific bucket.
459eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu         */
460eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private void buildLookupIndex() {
461eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            int numBuckets = mBuckets.length;
462eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mUnifiedLookupIndex = new int[mNumObjects + numBuckets];
463eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            int currentUnifiedIndexEntry = 0;
464eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            int nextUnifiedEntry;
465eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
466eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mMtpObjects = new MtpObjectInfo[mNumObjects];
467eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            int currentItemsEntry = 0;
468eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            for (int i = 0; i < numBuckets; i++) {
469eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                DateBucket bucket = mBuckets[i];
470eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                nextUnifiedEntry = currentUnifiedIndexEntry + bucket.tempElementsList.size() + 1;
471eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                Arrays.fill(mUnifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
472eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.unifiedStartIndex = currentUnifiedIndexEntry;
473eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.unifiedEndIndex = nextUnifiedEntry - 1;
474eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                currentUnifiedIndexEntry = nextUnifiedEntry;
475eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
476eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.itemsStartIndex = currentItemsEntry;
477c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                bucket.numItems = bucket.tempElementsList.size();
478c8a9e86919dca8938948f97efc3dcbe143e806bfBobby Georgescu                for (int j = 0; j < bucket.numItems; j++) {
479eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j);
480eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    currentItemsEntry++;
481eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                }
482eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.tempElementsList = null;
483d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
484d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
485d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
486eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private void copyResults() {
487eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            MtpDeviceIndex.this.mUnifiedLookupIndex = mUnifiedLookupIndex;
488eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            MtpDeviceIndex.this.mMtpObjects = mMtpObjects;
489eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            MtpDeviceIndex.this.mBuckets = mBuckets;
490eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mUnifiedLookupIndex = null;
491eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mMtpObjects = null;
492eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mBuckets = null;
493d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
494eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
495eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        @Override
496eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        public void run() {
497eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            try {
498eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                indexDevice();
499eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            } catch (IndexingException e) {
500eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                synchronized (MtpDeviceIndex.this) {
501eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    resetState();
502eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    if (mProgressListener != null) {
503eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                        mProgressListener.onIndexFinish();
504d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                    }
505d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu                }
506d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
507d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
508eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
509eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private void indexDevice() throws IndexingException {
510eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            synchronized (MtpDeviceIndex.this) {
511eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mProgress = Progress.Started;
512d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
513eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mBucketsTemp = new HashMap<SimpleDate, DateBucket>();
514eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            for (int storageId : mDevice.getStorageIds()) {
515eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                if (mDevice != getDevice()) throw new IndexingException();
516eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                Stack<Integer> pendingDirectories = new Stack<Integer>();
517eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
518eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                while (!pendingDirectories.isEmpty()) {
519eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    if (mDevice != getDevice()) throw new IndexingException();
520eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    int dirHandle = pendingDirectories.pop();
521eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
522eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                        MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle);
523eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                        if (objectInfo == null) throw new IndexingException();
524baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                        int format = objectInfo.getFormat();
525baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                        if (format == MtpConstants.FORMAT_ASSOCIATION) {
526baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                            pendingDirectories.add(objectHandle);
527baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                        } else if (SUPPORTED_IMAGE_FORMATS.contains(format)
528baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                                || SUPPORTED_VIDEO_FORMATS.contains(format)) {
529baa68886db66f4beb6a07e4441b122975b778f1cBobby Georgescu                            addObject(objectInfo);
530eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                        }
531eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    }
532eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                }
533d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            }
534eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            Collection<DateBucket> values = mBucketsTemp.values();
535eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mBucketsTemp = null;
536eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mBuckets = values.toArray(new DateBucket[values.size()]);
537eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            values = null;
538eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            synchronized (MtpDeviceIndex.this) {
539eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mProgress = Progress.Sorting;
540eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                if (mProgressListener != null) {
541eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    mProgressListener.onSorting();
542eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                }
543eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            }
544eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            sortAll();
545eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            buildLookupIndex();
546eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            synchronized (MtpDeviceIndex.this) {
547eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                if (mDevice != getDevice()) throw new IndexingException();
548eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                copyResults();
549eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
550eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                /*
551eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * In order for getBuckets to operate in constant time for descending
552eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * order, we must precompute a reversed array of the buckets, mainly
553eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * because the android.widget.SectionIndexer interface which adapters
554eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * that call getBuckets implement depends on section numbers to be
555eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * ascending relative to the scroll position, so we must have this for
556eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 * descending order or the scrollbar goes crazy.
557eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                 */
558eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                computeReversedBuckets();
559d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
560eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mProgress = Progress.Finished;
561eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                if (mProgressListener != null) {
562eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                    mProgressListener.onIndexFinish();
563eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                }
564eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            }
565d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
566eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
567eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private SimpleDate mDateInstance = new SimpleDate();
568eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu
569eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private void addObject(MtpObjectInfo objectInfo) {
570eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mNumObjects++;
571eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            mDateInstance.setTimestamp(objectInfo.getDateCreated());
572eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            DateBucket bucket = mBucketsTemp.get(mDateInstance);
573eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            if (bucket == null) {
574eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket = new DateBucket(mDateInstance, objectInfo);
575eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mBucketsTemp.put(mDateInstance, bucket);
576eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mDateInstance = new SimpleDate(); // only create new date
577eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                                                  // objects when they are used
578eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                return;
579eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            } else {
580eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.tempElementsList.add(objectInfo);
581eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            }
582eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            if (mProgressListener != null) {
583eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                mProgressListener.onObjectIndexed(objectInfo, mNumObjects);
584eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            }
585d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
586d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
587eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu        private void sortAll() {
588eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            Arrays.sort(mBuckets);
589eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            for (DateBucket bucket : mBuckets) {
590eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu                bucket.sortElements(sMtpObjectComparator);
591eebf9e81673150a6c4fec89b64e3cdb595377c83Bobby Georgescu            }
592d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
593d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
594d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
595d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu
596d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    private void computeReversedBuckets() {
597d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        mCachedReverseBuckets = new Object[mBuckets.length];
598d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        for (int i = 0; i < mCachedReverseBuckets.length; i++) {
599d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu            mCachedReverseBuckets[i] = mBuckets[mBuckets.length - 1 - i];
600d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu        }
601d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu    }
602d3aac52ffb88ced53413d5eef29c641dd6982267Bobby Georgescu}
603