blob: 580e6bcb9e2ecfbfd9f202753a8007f6ae0dfe05 [file] [log] [blame]
Ben Kwa0497da82015-11-30 23:00:02 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui.dirlist;
18
19import static com.android.documentsui.Shared.DEBUG;
Steve McKayd0805062016-09-15 14:30:38 -070020import static com.android.documentsui.base.DocumentInfo.getCursorLong;
21import static com.android.documentsui.base.DocumentInfo.getCursorString;
Ben Kwa0497da82015-11-30 23:00:02 -080022
Ben Kwa0497da82015-11-30 23:00:02 -080023import android.database.Cursor;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090024import android.database.MergeCursor;
Steve McKay84769b82016-06-09 10:46:07 -070025import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080026import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080027import android.provider.DocumentsContract;
28import android.provider.DocumentsContract.Document;
29import android.support.annotation.Nullable;
30import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080031import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080032
Ben Kwa0497da82015-11-30 23:00:02 -080033import com.android.documentsui.DirectoryResult;
Ben Kwa0497da82015-11-30 23:00:02 -080034import com.android.documentsui.RootCursorWrapper;
Steve McKay55c00e72016-02-18 15:32:16 -080035import com.android.documentsui.Shared;
Steve McKayd0805062016-09-15 14:30:38 -070036import com.android.documentsui.base.DocumentInfo;
Ben Kwa0497da82015-11-30 23:00:02 -080037import com.android.documentsui.dirlist.MultiSelectManager.Selection;
Garfield, Tan11d23482016-08-05 09:33:29 -070038import com.android.documentsui.sorting.SortDimension;
39import com.android.documentsui.sorting.SortModel;
Ben Kwa0497da82015-11-30 23:00:02 -080040
41import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080042import java.util.HashMap;
43import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080044import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080045
46/**
47 * The data model for the current loaded directory.
48 */
49@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090050public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080051 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080052
Ben Kwa0497da82015-11-30 23:00:02 -080053 private boolean mIsLoading;
Ben Kwad72a1da2015-12-01 19:56:57 -080054 private List<UpdateListener> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080055 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080056 private int mCursorCount;
57 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
58 private Map<String, Integer> mPositions = new HashMap<>();
59 /**
60 * A sorted array of model IDs for the files currently in the Model. Sort order is determined
Garfield, Tan11d23482016-08-05 09:33:29 -070061 * by {@link #mSortModel}
Ben Kwab8a5e082015-12-07 13:25:27 -080062 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090063 private String mIds[] = new String[0];
Garfield, Tan11d23482016-08-05 09:33:29 -070064 private SortModel mSortModel;
Ben Kwab8a5e082015-12-07 13:25:27 -080065
Ben Kwa0497da82015-11-30 23:00:02 -080066 @Nullable String info;
67 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090068 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080069
Ben Kwad72a1da2015-12-01 19:56:57 -080070 private void notifyUpdateListeners() {
71 for (UpdateListener listener: mUpdateListeners) {
72 listener.onModelUpdate(this);
73 }
74 }
75
76 private void notifyUpdateListeners(Exception e) {
77 for (UpdateListener listener: mUpdateListeners) {
78 listener.onModelUpdateFailed(e);
79 }
80 }
81
Steve McKay9de0da62016-08-25 15:18:23 -070082 void onLoaderReset() {
83 if (mIsLoading) {
Steve McKay7c662092016-08-26 12:17:41 -070084 Log.w(TAG, "Received unexpected loader reset while in loading state for doc: "
85 + DocumentInfo.debugString(doc));
86 return;
Ben Kwa0497da82015-11-30 23:00:02 -080087 }
Steve McKay7c662092016-08-26 12:17:41 -070088
89 reset();
Steve McKay9de0da62016-08-25 15:18:23 -070090 }
91
92 private void reset() {
93 mCursor = null;
94 mCursorCount = 0;
95 mIds = new String[0];
96 mPositions.clear();
97 info = null;
98 error = null;
99 doc = null;
100 mIsLoading = false;
101 notifyUpdateListeners();
102 }
103
104 void update(DirectoryResult result) {
105 assert(result != null);
106
107 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
Ben Kwa0497da82015-11-30 23:00:02 -0800108
109 if (result.exception != null) {
110 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800111 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800112 return;
113 }
114
115 mCursor = result.cursor;
116 mCursorCount = mCursor.getCount();
Garfield, Tan11d23482016-08-05 09:33:29 -0700117 mSortModel = result.sortModel;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900118 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800119
Ben Kwab8a5e082015-12-07 13:25:27 -0800120 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800121
122 final Bundle extras = mCursor.getExtras();
123 if (extras != null) {
124 info = extras.getString(DocumentsContract.EXTRA_INFO);
125 error = extras.getString(DocumentsContract.EXTRA_ERROR);
126 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
127 }
128
Ben Kwad72a1da2015-12-01 19:56:57 -0800129 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800130 }
131
Ben Kwad72a1da2015-12-01 19:56:57 -0800132 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800133 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800134 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800135 }
136
137 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800138 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
139 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800140 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800141 private void updateModelData() {
142 int[] positions = new int[mCursorCount];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900143 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900144 boolean[] isDirs = new boolean[mCursorCount];
145 String[] displayNames = null;
Ben Kwa6280de02015-12-16 19:42:08 -0800146 long[] longValues = null;
Ben Kwab8a5e082015-12-07 13:25:27 -0800147
Garfield, Tan11d23482016-08-05 09:33:29 -0700148 final int id = mSortModel.getSortedDimensionId();
149 switch (id) {
150 case SortModel.SORT_DIMENSION_ID_TITLE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900151 displayNames = new String[mCursorCount];
152 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700153 case SortModel.SORT_DIMENSION_ID_DATE:
154 case SortModel.SORT_DIMENSION_ID_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900155 longValues = new long[mCursorCount];
156 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800157 }
158
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900159 String mimeType;
160
Ben Kwa0497da82015-11-30 23:00:02 -0800161 mCursor.moveToPosition(-1);
162 for (int pos = 0; pos < mCursorCount; ++pos) {
Ben Lin2df30e52016-04-22 16:12:50 -0700163 if (!mCursor.moveToNext()) {
164 Log.e(TAG, "Fail to move cursor to next pos: " + pos);
165 return;
166 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800167 positions[pos] = pos;
Ben Kwab8a5e082015-12-07 13:25:27 -0800168
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900169 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
170 // unique string that can be used to identify the document referred to by the cursor.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900171 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
172 // with the authority to avoid collisions.
173 if (mCursor instanceof MergeCursor) {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900174 mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY) + "|" +
175 getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900176 } else {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900177 mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900178 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900179
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900180 mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900181 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
182
Garfield, Tan11d23482016-08-05 09:33:29 -0700183 switch(id) {
184 case SortModel.SORT_DIMENSION_ID_TITLE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900185 final String displayName = getCursorString(
186 mCursor, Document.COLUMN_DISPLAY_NAME);
187 displayNames[pos] = displayName;
Ben Kwab8a5e082015-12-07 13:25:27 -0800188 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700189 case SortModel.SORT_DIMENSION_ID_DATE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900190 longValues[pos] = getLastModified(mCursor);
Ben Kwab8a5e082015-12-07 13:25:27 -0800191 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700192 case SortModel.SORT_DIMENSION_ID_SIZE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900193 longValues[pos] = getCursorLong(mCursor, Document.COLUMN_SIZE);
Ben Kwab8a5e082015-12-07 13:25:27 -0800194 break;
195 }
196 }
197
Garfield, Tan11d23482016-08-05 09:33:29 -0700198 final SortDimension dimension = mSortModel.getDimensionById(id);
199 switch (id) {
200 case SortModel.SORT_DIMENSION_ID_TITLE:
201 binarySort(displayNames, isDirs, positions, mIds, dimension.getSortDirection());
Ben Kwab8a5e082015-12-07 13:25:27 -0800202 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700203 case SortModel.SORT_DIMENSION_ID_DATE:
204 case SortModel.SORT_DIMENSION_ID_SIZE:
205 binarySort(longValues, isDirs, positions, mIds, dimension.getSortDirection());
Ben Kwab8a5e082015-12-07 13:25:27 -0800206 break;
207 }
208
209 // Populate the positions.
210 mPositions.clear();
211 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900212 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800213 }
214 }
215
216 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800217 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900218 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
219 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800220 *
221 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900222 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800223 * @param positions Cursor positions to be sorted.
224 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800225 */
Garfield, Tan11d23482016-08-05 09:33:29 -0700226 private static void binarySort(
227 String[] sortKey,
228 boolean[] isDirs,
229 int[] positions,
230 String[] ids,
231 @SortDimension.SortDirection int direction) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800232 final int count = positions.length;
233 for (int start = 1; start < count; start++) {
234 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800235 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900236 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900237 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800238
239 int left = 0;
240 int right = start;
241
242 while (left < right) {
243 int mid = (left + right) >>> 1;
244
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900245 // Directories always go in front.
246 int compare = 0;
247 final boolean rhsIsDir = isDirs[mid];
248 if (pivotIsDir && !rhsIsDir) {
249 compare = -1;
250 } else if (!pivotIsDir && rhsIsDir) {
251 compare = 1;
252 } else {
253 final String lhs = pivotValue;
254 final String rhs = sortKey[mid];
Garfield, Tan11d23482016-08-05 09:33:29 -0700255 switch (direction) {
256 case SortDimension.SORT_DIRECTION_ASCENDING:
257 compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
258 break;
259 case SortDimension.SORT_DIRECTION_DESCENDING:
260 compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
261 break;
262 default:
263 throw new IllegalArgumentException(
264 "Unknown sorting direction: " + direction);
265 }
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900266 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800267
268 if (compare < 0) {
269 right = mid;
270 } else {
271 left = mid + 1;
272 }
273 }
274
275 int n = start - left;
276 switch (n) {
277 case 2:
278 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800279 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900280 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900281 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800282 case 1:
283 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800284 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900285 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900286 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800287 break;
288 default:
289 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800290 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900291 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900292 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800293 }
294
295 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800296 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900297 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900298 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800299 }
300 }
301
302 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800303 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
304 * key, and the second is an array of mime types. The rows are first bucketed by mime type
305 * (directories vs documents) and then each bucket is sorted independently in descending
306 * numerical order on the sort key. This code is based on TimSort.binarySort().
307 *
308 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900309 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800310 * @param positions Cursor positions to be sorted.
311 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800312 */
Ben Kwa6280de02015-12-16 19:42:08 -0800313 private static void binarySort(
Garfield, Tan11d23482016-08-05 09:33:29 -0700314 long[] sortKey,
315 boolean[] isDirs,
316 int[] positions,
317 String[] ids,
318 @SortDimension.SortDirection int direction) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800319 final int count = positions.length;
320 for (int start = 1; start < count; start++) {
321 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800322 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900323 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900324 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800325
326 int left = 0;
327 int right = start;
328
329 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800330 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800331
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900332 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800333 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900334 final boolean rhsIsDir = isDirs[mid];
335 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800336 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900337 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800338 compare = 1;
339 } else {
340 final long lhs = pivotValue;
341 final long rhs = sortKey[mid];
Garfield, Tan11d23482016-08-05 09:33:29 -0700342 switch (direction) {
343 case SortDimension.SORT_DIRECTION_ASCENDING:
344 compare = Long.compare(lhs, rhs);
345 break;
346 case SortDimension.SORT_DIRECTION_DESCENDING:
347 compare = -Long.compare(lhs, rhs);
348 break;
349 default:
350 throw new IllegalArgumentException(
351 "Unknown sorting direction: " + direction);
352 }
Ben Kwa6280de02015-12-16 19:42:08 -0800353 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800354
Ben Kwae4338342016-01-08 15:34:47 -0800355 // If numerical comparison yields a tie, use document ID as a tie breaker. This
356 // will yield stable results even if incoming items are continually shuffling and
357 // have identical numerical sort keys. One common example of this scenario is seen
358 // when sorting a set of active downloads by mod time.
359 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900360 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800361 }
362
Ben Kwab8a5e082015-12-07 13:25:27 -0800363 if (compare < 0) {
364 right = mid;
365 } else {
366 left = mid + 1;
367 }
368 }
369
370 int n = start - left;
371 switch (n) {
372 case 2:
373 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800374 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900375 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900376 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800377 case 1:
378 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800379 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900380 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900381 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800382 break;
383 default:
384 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800385 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900386 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900387 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800388 }
389
390 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800391 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900392 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900393 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800394 }
395 }
396
Ben Kwae4338342016-01-08 15:34:47 -0800397 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900398 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900399 * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
Garfield, Tan11d23482016-08-05 09:33:29 -0700400 * when sorting descending by date.
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900401 */
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900402 long getLastModified(Cursor cursor) {
403 long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
404 return (l == -1) ? Long.MAX_VALUE : l;
Ben Kwae4338342016-01-08 15:34:47 -0800405 }
406
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900407 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800408 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700409 if (pos == null) {
410 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
411 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800412 }
Steve McKay5a22a112016-04-12 11:29:10 -0700413
414 if (!mCursor.moveToPosition(pos)) {
415 if (DEBUG) Log.d(TAG,
416 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
417 return null;
418 }
419
420 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800421 }
422
Ben Kwa0497da82015-11-30 23:00:02 -0800423 boolean isEmpty() {
424 return mCursorCount == 0;
425 }
426
427 boolean isLoading() {
428 return mIsLoading;
429 }
430
Steve McKay84769b82016-06-09 10:46:07 -0700431 List<DocumentInfo> getDocuments(Selection selection) {
432 final int size = (selection != null) ? selection.size() : 0;
Ben Kwa0497da82015-11-30 23:00:02 -0800433
434 final List<DocumentInfo> docs = new ArrayList<>(size);
Steve McKay84769b82016-06-09 10:46:07 -0700435 // NOTE: That as this now iterates over only final (non-provisional) selection.
436 for (String modelId: selection) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800437 final Cursor cursor = getItem(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700438 if (cursor == null) {
Steve McKay84769b82016-06-09 10:46:07 -0700439 Log.w(TAG, "Skipping document. Unabled to obtain cursor for modelId: " + modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700440 continue;
441 }
Steve McKay0af8afd2016-02-25 13:34:03 -0800442 docs.add(DocumentInfo.fromDirectoryCursor(cursor));
Ben Kwa0497da82015-11-30 23:00:02 -0800443 }
444 return docs;
445 }
446
Steve McKay84769b82016-06-09 10:46:07 -0700447 public Uri getItemUri(String modelId) {
448 final Cursor cursor = getItem(modelId);
449 return DocumentInfo.getUri(cursor);
450 }
451
Garfield, Tan11d23482016-08-05 09:33:29 -0700452 /**
453 * @return An ordered array of model IDs representing the documents in the model. It is sorted
454 * according to the current sort order, which was set by the last model update.
455 */
456 public String[] getModelIds() {
457 return mIds;
458 }
459
Ben Kwa0497da82015-11-30 23:00:02 -0800460 void addUpdateListener(UpdateListener listener) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800461 mUpdateListeners.add(listener);
Ben Kwa0497da82015-11-30 23:00:02 -0800462 }
463
Ben Kwa472103f2016-02-10 15:48:25 -0800464 void removeUpdateListener(UpdateListener listener) {
465 mUpdateListeners.remove(listener);
466 }
467
Garfield, Tan11d23482016-08-05 09:33:29 -0700468 interface UpdateListener {
Ben Kwa0497da82015-11-30 23:00:02 -0800469 /**
470 * Called when a successful update has occurred.
471 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800472 void onModelUpdate(Model model);
Ben Kwa0497da82015-11-30 23:00:02 -0800473
474 /**
475 * Called when an update has been attempted but failed.
476 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800477 void onModelUpdateFailed(Exception e);
Ben Kwa0497da82015-11-30 23:00:02 -0800478 }
Ben Kwa0497da82015-11-30 23:00:02 -0800479}