1003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa/*
2003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * Copyright (C) 2015 The Android Open Source Project
3003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa *
4003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * Licensed under the Apache License, Version 2.0 (the "License");
5003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * you may not use this file except in compliance with the License.
6003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * You may obtain a copy of the License at
7003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa *
8003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa *      http://www.apache.org/licenses/LICENSE-2.0
9003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa *
10003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * Unless required by applicable law or agreed to in writing, software
11003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * distributed under the License is distributed on an "AS IS" BASIS,
12003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * See the License for the specific language governing permissions and
14003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * limitations under the License.
15003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa */
16003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
17003a999db6fef1e0bcec415d02a6b96926942448Ben Kwapackage com.android.documentsui.dirlist;
18003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
19003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport static com.android.documentsui.Shared.DEBUG;
20862b9641e39997f5189dadf0f0c6776f911f344eBen Kwaimport static com.android.documentsui.State.SORT_ORDER_DISPLAY_NAME;
21862b9641e39997f5189dadf0f0c6776f911f344eBen Kwaimport static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED;
22862b9641e39997f5189dadf0f0c6776f911f344eBen Kwaimport static com.android.documentsui.State.SORT_ORDER_SIZE;
23f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewskiimport static com.android.documentsui.model.DocumentInfo.getCursorLong;
24f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewskiimport static com.android.documentsui.model.DocumentInfo.getCursorString;
25003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
26003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.database.Cursor;
27007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewskiimport android.database.MergeCursor;
28003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.os.Bundle;
29003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.provider.DocumentsContract;
30003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.provider.DocumentsContract.Document;
31003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.support.annotation.Nullable;
32003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.support.annotation.VisibleForTesting;
33003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport android.util.Log;
34003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
35003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport com.android.documentsui.DirectoryResult;
36003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport com.android.documentsui.RootCursorWrapper;
37008e948c3eac913ae3321bd690e3913e468e7fb1Steve McKayimport com.android.documentsui.Shared;
38003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport com.android.documentsui.dirlist.MultiSelectManager.Selection;
39003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport com.android.documentsui.model.DocumentInfo;
40003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
41003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport java.util.ArrayList;
42dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKayimport java.util.Collections;
43003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport java.util.HashMap;
44003a999db6fef1e0bcec415d02a6b96926942448Ben Kwaimport java.util.List;
45862b9641e39997f5189dadf0f0c6776f911f344eBen Kwaimport java.util.Map;
46003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
47003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa/**
48003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa * The data model for the current loaded directory.
49003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa */
50003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa@VisibleForTesting
513d988a969f39ca34ef89c449a0675164499c9a3aTomasz Mikolajewskipublic class Model {
52003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    private static final String TAG = "Model";
53862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
54003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    private boolean mIsLoading;
55743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    private List<UpdateListener> mUpdateListeners = new ArrayList<>();
56003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    @Nullable private Cursor mCursor;
57862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    private int mCursorCount;
58862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    /** Maps Model ID to cursor positions, for looking up items by Model ID. */
59862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    private Map<String, Integer> mPositions = new HashMap<>();
60862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    /**
61862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     * A sorted array of model IDs for the files currently in the Model.  Sort order is determined
62862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     * by {@link #mSortOrder}
63862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     */
64c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski    private String mIds[] = new String[0];
65862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    private int mSortOrder = SORT_ORDER_DISPLAY_NAME;
66862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
67003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    @Nullable String info;
68003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    @Nullable String error;
69ae6d6b494c2f5837889ebe8cbd50d6782697884fTomasz Mikolajewski    @Nullable DocumentInfo doc;
70003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
71743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    private void notifyUpdateListeners() {
72743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        for (UpdateListener listener: mUpdateListeners) {
73743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa            listener.onModelUpdate(this);
74743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        }
75743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    }
76743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa
77743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    private void notifyUpdateListeners(Exception e) {
78743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        for (UpdateListener listener: mUpdateListeners) {
79743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa            listener.onModelUpdateFailed(e);
80743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        }
81743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    }
82743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa
83003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    void update(DirectoryResult result) {
84003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        if (DEBUG) Log.i(TAG, "Updating model with new result set.");
85003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
86003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        if (result == null) {
87003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            mCursor = null;
88003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            mCursorCount = 0;
89c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            mIds = new String[0];
90862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            mPositions.clear();
91003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            info = null;
92003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            error = null;
93ae6d6b494c2f5837889ebe8cbd50d6782697884fTomasz Mikolajewski            doc = null;
94003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            mIsLoading = false;
95743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa            notifyUpdateListeners();
96003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            return;
97003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
98003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
99003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        if (result.exception != null) {
100003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            Log.e(TAG, "Error while loading directory contents", result.exception);
101743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa            notifyUpdateListeners(result.exception);
102003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            return;
103003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
104003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
105003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        mCursor = result.cursor;
106003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        mCursorCount = mCursor.getCount();
107862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        mSortOrder = result.sortOrder;
108ae6d6b494c2f5837889ebe8cbd50d6782697884fTomasz Mikolajewski        doc = result.doc;
109003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
110862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        updateModelData();
111003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
112003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        final Bundle extras = mCursor.getExtras();
113003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        if (extras != null) {
114003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            info = extras.getString(DocumentsContract.EXTRA_INFO);
115003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            error = extras.getString(DocumentsContract.EXTRA_ERROR);
116003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa            mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
117003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
118003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
119743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        notifyUpdateListeners();
120003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
121003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
122743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    @VisibleForTesting
123003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    int getItemCount() {
124db65cd5e48e847e99cc64bf79a2fb7fe3bbfd31aBen Kwa        return mCursorCount;
125003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
126003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
127003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    /**
128862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
129862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     * according to the current sort order.
130003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa     */
131862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    private void updateModelData() {
132862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        int[] positions = new int[mCursorCount];
133c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski        mIds = new String[mCursorCount];
13477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski        boolean[] isDirs = new boolean[mCursorCount];
13577f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski        String[] displayNames = null;
136c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa        long[] longValues = null;
137862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
13877f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski        switch (mSortOrder) {
13977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            case SORT_ORDER_DISPLAY_NAME:
14077f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                displayNames = new String[mCursorCount];
14177f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                break;
14277f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            case SORT_ORDER_LAST_MODIFIED:
14377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            case SORT_ORDER_SIZE:
14477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                longValues = new long[mCursorCount];
14577f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                break;
146862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        }
147862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
14877f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski        String mimeType;
14977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski
150003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        mCursor.moveToPosition(-1);
151003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        for (int pos = 0; pos < mCursorCount; ++pos) {
15271c7565093fcdc5c58dd16a606f528fff3002177Ben Lin            if (!mCursor.moveToNext()) {
15371c7565093fcdc5c58dd16a606f528fff3002177Ben Lin                Log.e(TAG, "Fail to move cursor to next pos: " + pos);
15471c7565093fcdc5c58dd16a606f528fff3002177Ben Lin                return;
15571c7565093fcdc5c58dd16a606f528fff3002177Ben Lin            }
156862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            positions[pos] = pos;
157862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
1582e4e14789ecf923b16ea4e79b5605952ddc56e5bTomasz Mikolajewski            // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
1592e4e14789ecf923b16ea4e79b5605952ddc56e5bTomasz Mikolajewski            // unique string that can be used to identify the document referred to by the cursor.
160007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewski            // If the cursor is a merged cursor over multiple authorities, then prefix the ids
161007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewski            // with the authority to avoid collisions.
162007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewski            if (mCursor instanceof MergeCursor) {
163f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY) + "|" +
164f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                        getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
165007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewski            } else {
166f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
167007151a90d2a97ad17772a3c9e6be249261a5365Tomasz Mikolajewski            }
1682e4e14789ecf923b16ea4e79b5605952ddc56e5bTomasz Mikolajewski
169f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski            mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
17077f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
17177f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski
172f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski            switch(mSortOrder) {
173862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case SORT_ORDER_DISPLAY_NAME:
174f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                    final String displayName = getCursorString(
175f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                            mCursor, Document.COLUMN_DISPLAY_NAME);
176f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                    displayNames[pos] = displayName;
177862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    break;
178862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case SORT_ORDER_LAST_MODIFIED:
179f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                    longValues[pos] = getLastModified(mCursor);
180862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    break;
181862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case SORT_ORDER_SIZE:
182f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                    longValues[pos] = getCursorLong(mCursor, Document.COLUMN_SIZE);
183862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    break;
184862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            }
185862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        }
186862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
187862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        switch (mSortOrder) {
188862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            case SORT_ORDER_DISPLAY_NAME:
18977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                binarySort(displayNames, isDirs, positions, mIds);
190862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                break;
191862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            case SORT_ORDER_LAST_MODIFIED:
192862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            case SORT_ORDER_SIZE:
19377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                binarySort(longValues, isDirs, positions, mIds);
194862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                break;
195862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        }
196862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
197862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        // Populate the positions.
198862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        mPositions.clear();
199862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        for (int i = 0; i < mCursorCount; ++i) {
200c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            mPositions.put(mIds[i], positions[i]);
201862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        }
202862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    }
203862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
204862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    /**
205c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * Sorts model data. Takes three columns of index-corresponded data. The first column is the
20677f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski     * sort key. Rows are sorted in ascending alphabetical order on the sort key.
20777f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski     * Directories are always shown first. This code is based on TimSort.binarySort().
208c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     *
209c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param sortKey Data is sorted in ascending alphabetical order.
21077f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski     * @param isDirs Array saying whether an item is a directory or not.
211c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param positions Cursor positions to be sorted.
212c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param ids Model IDs to be sorted.
213862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     */
21477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski    private static void binarySort(String[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
215862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        final int count = positions.length;
216862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        for (int start = 1; start < count; start++) {
217862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            final int pivotPosition = positions[start];
218c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa            final String pivotValue = sortKey[start];
21977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            final boolean pivotIsDir = isDirs[start];
220c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            final String pivotId = ids[start];
221862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
222862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int left = 0;
223862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int right = start;
224862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
225862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            while (left < right) {
226862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                int mid = (left + right) >>> 1;
227862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
22877f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                // Directories always go in front.
22977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                int compare = 0;
23077f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                final boolean rhsIsDir = isDirs[mid];
23177f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                if (pivotIsDir && !rhsIsDir) {
23277f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    compare = -1;
23377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                } else if (!pivotIsDir && rhsIsDir) {
23477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    compare = 1;
23577f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                } else {
23677f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    final String lhs = pivotValue;
23777f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    final String rhs = sortKey[mid];
238f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski                    compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
23977f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                }
240862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
241862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                if (compare < 0) {
242862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    right = mid;
243862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                } else {
244862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    left = mid + 1;
245862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                }
246862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            }
247862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
248862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int n = start - left;
249862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            switch (n) {
250862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case 2:
251862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    positions[left + 2] = positions[left + 1];
252c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    sortKey[left + 2] = sortKey[left + 1];
25377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    isDirs[left + 2] = isDirs[left + 1];
254c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    ids[left + 2] = ids[left + 1];
255862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case 1:
256862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    positions[left + 1] = positions[left];
257c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    sortKey[left + 1] = sortKey[left];
25877f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    isDirs[left + 1] = isDirs[left];
259c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    ids[left + 1] = ids[left];
260862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    break;
261862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                default:
262862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    System.arraycopy(positions, left, positions, left + 1, n);
263c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    System.arraycopy(sortKey, left, sortKey, left + 1, n);
26477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    System.arraycopy(isDirs, left, isDirs, left + 1, n);
265c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    System.arraycopy(ids, left, ids, left + 1, n);
266862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            }
267862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
268862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            positions[left] = pivotPosition;
269c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa            sortKey[left] = pivotValue;
27077f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            isDirs[left] = pivotIsDir;
271c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            ids[left] = pivotId;
272862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        }
273862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    }
274862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
275862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    /**
276c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
277c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * key, and the second is an array of mime types. The rows are first bucketed by mime type
278c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * (directories vs documents) and then each bucket is sorted independently in descending
279c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * numerical order on the sort key. This code is based on TimSort.binarySort().
280c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     *
281c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param sortKey Data is sorted in descending numerical order.
28277f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski     * @param isDirs Array saying whether an item is a directory or not.
283c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param positions Cursor positions to be sorted.
284c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa     * @param ids Model IDs to be sorted.
285862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     */
286c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa    private static void binarySort(
28777f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            long[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
288862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        final int count = positions.length;
289862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        for (int start = 1; start < count; start++) {
290862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            final int pivotPosition = positions[start];
291c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa            final long pivotValue = sortKey[start];
29277f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            final boolean pivotIsDir = isDirs[start];
293c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            final String pivotId = ids[start];
294862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
295862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int left = 0;
296862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int right = start;
297862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
298862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            while (left < right) {
299c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                int mid = ((left + right) >>> 1);
300c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa
30177f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                // Directories always go in front.
302c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                int compare = 0;
30377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                final boolean rhsIsDir = isDirs[mid];
30477f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                if (pivotIsDir && !rhsIsDir) {
305c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    compare = -1;
30677f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                } else if (!pivotIsDir && rhsIsDir) {
307c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    compare = 1;
308c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                } else {
309c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    final long lhs = pivotValue;
310c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    final long rhs = sortKey[mid];
311e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                    // Sort in descending numerical order. This matches legacy behaviour, which
312e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                    // yields largest or most recent items on top.
313c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    compare = -Long.compare(lhs, rhs);
314c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                }
315862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
316e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                // If numerical comparison yields a tie, use document ID as a tie breaker.  This
317e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                // will yield stable results even if incoming items are continually shuffling and
318e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                // have identical numerical sort keys.  One common example of this scenario is seen
319e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                // when sorting a set of active downloads by mod time.
320e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                if (compare == 0) {
321c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    compare = pivotId.compareTo(ids[mid]);
322e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa                }
323e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa
324862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                if (compare < 0) {
325862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    right = mid;
326862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                } else {
327862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    left = mid + 1;
328862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                }
329862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            }
330862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
331862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            int n = start - left;
332862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            switch (n) {
333862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case 2:
334862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    positions[left + 2] = positions[left + 1];
335c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    sortKey[left + 2] = sortKey[left + 1];
33677f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    isDirs[left + 2] = isDirs[left + 1];
337c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    ids[left + 2] = ids[left + 1];
338862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                case 1:
339862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    positions[left + 1] = positions[left];
340c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    sortKey[left + 1] = sortKey[left];
34177f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    isDirs[left + 1] = isDirs[left];
342c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    ids[left + 1] = ids[left];
343862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    break;
344862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                default:
345862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa                    System.arraycopy(positions, left, positions, left + 1, n);
346c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa                    System.arraycopy(sortKey, left, sortKey, left + 1, n);
34777f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski                    System.arraycopy(isDirs, left, isDirs, left + 1, n);
348c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski                    System.arraycopy(ids, left, ids, left + 1, n);
349862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            }
350862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
351862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa            positions[left] = pivotPosition;
352c72a2cb899acf5562288675435b08d91ccf8019dBen Kwa            sortKey[left] = pivotValue;
35377f0595cd150dfb9e1b799cc1b32cb9c32fbc47cTomasz Mikolajewski            isDirs[left] = pivotIsDir;
354c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski            ids[left] = pivotId;
355003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
356003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
357003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
358e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa    /**
359e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa     * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
360f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski     * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
361f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski     * when sorting by date.
3622e4e14789ecf923b16ea4e79b5605952ddc56e5bTomasz Mikolajewski     */
363f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski    long getLastModified(Cursor cursor) {
364f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski        long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
365f61b0b844069116adde26df6d4655d33831bb395Tomasz Mikolajewski        return (l == -1) ? Long.MAX_VALUE : l;
366e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa    }
367e2564f9f197f6398cf3f2b1310045b8f685fd85cBen Kwa
3683d988a969f39ca34ef89c449a0675164499c9a3aTomasz Mikolajewski    public @Nullable Cursor getItem(String modelId) {
369003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        Integer pos = mPositions.get(modelId);
370dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay        if (pos == null) {
371dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
372dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            return null;
373003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
374dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay
375dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay        if (!mCursor.moveToPosition(pos)) {
376dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            if (DEBUG) Log.d(TAG,
377dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay                    "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
378dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            return null;
379dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay        }
380dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay
381dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay        return mCursor;
382003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
383003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
384003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    boolean isEmpty() {
385003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        return mCursorCount == 0;
386003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
387003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
388003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    boolean isLoading() {
389003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        return mIsLoading;
390003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
391003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
392003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    List<DocumentInfo> getDocuments(Selection items) {
393003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        final int size = (items != null) ? items.size() : 0;
394003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
395003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        final List<DocumentInfo> docs =  new ArrayList<>(size);
396743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        for (String modelId: items.getAll()) {
397743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa            final Cursor cursor = getItem(modelId);
398dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            if (cursor == null) {
399dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay                Log.w(TAG,
400dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay                        "Skipping document. Unabled to obtain cursor for modelId: " + modelId);
401dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay                continue;
402dcc68fdd0ca1f0d2d2dfb979dd837ac2dd2e16f3Steve McKay            }
403a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay            docs.add(DocumentInfo.fromDirectoryCursor(cursor));
404003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        }
405003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        return docs;
406003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
407003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
408003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    void addUpdateListener(UpdateListener listener) {
409743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        mUpdateListeners.add(listener);
410003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
411003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
412a4acc90b0a0bbfa55ed247a3ae04c766c885d220Ben Kwa    void removeUpdateListener(UpdateListener listener) {
413a4acc90b0a0bbfa55ed247a3ae04c766c885d220Ben Kwa        mUpdateListeners.remove(listener);
414a4acc90b0a0bbfa55ed247a3ae04c766c885d220Ben Kwa    }
415a4acc90b0a0bbfa55ed247a3ae04c766c885d220Ben Kwa
416743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa    static interface UpdateListener {
417003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        /**
418003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa         * Called when a successful update has occurred.
419003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa         */
420743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        void onModelUpdate(Model model);
421003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa
422003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa        /**
423003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa         * Called when an update has been attempted but failed.
424003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa         */
425743c7c2f0f57b4cef7cc11154b2e804a7eb33177Ben Kwa        void onModelUpdateFailed(Exception e);
426003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa    }
427862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa
428862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    /**
429862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     * @return An ordered array of model IDs representing the documents in the model. It is sorted
430862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     *         according to the current sort order, which was set by the last model update.
431862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa     */
432c05d98f64e62f57c34e3e00b05261e94b781b1e3Tomasz Mikolajewski    public String[] getModelIds() {
433862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa        return mIds;
434862b9641e39997f5189dadf0f0c6776f911f344eBen Kwa    }
435003a999db6fef1e0bcec415d02a6b96926942448Ben Kwa}
436