blob: 7973101fe0fe43418cf45c25305b4c68aa3d5359 [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
Steve McKayd0805062016-09-15 14:30:38 -070019import static com.android.documentsui.base.DocumentInfo.getCursorLong;
20import static com.android.documentsui.base.DocumentInfo.getCursorString;
Steve McKayd9caa6a2016-09-15 16:36:45 -070021import static com.android.documentsui.base.Shared.DEBUG;
Ben Kwa0497da82015-11-30 23:00:02 -080022
Steve McKay990f76e2016-09-16 12:36:58 -070023import android.annotation.IntDef;
Ben Kwa0497da82015-11-30 23:00:02 -080024import android.database.Cursor;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090025import android.database.MergeCursor;
Steve McKay84769b82016-06-09 10:46:07 -070026import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080027import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080028import android.provider.DocumentsContract;
29import android.provider.DocumentsContract.Document;
30import android.support.annotation.Nullable;
31import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080032import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080033
Ben Kwa0497da82015-11-30 23:00:02 -080034import com.android.documentsui.DirectoryResult;
Steve McKayd0805062016-09-15 14:30:38 -070035import com.android.documentsui.base.DocumentInfo;
Steve McKay990f76e2016-09-16 12:36:58 -070036import com.android.documentsui.base.EventListener;
Steve McKayd9caa6a2016-09-15 16:36:45 -070037import com.android.documentsui.base.Shared;
Ben Kwa0497da82015-11-30 23:00:02 -080038import com.android.documentsui.dirlist.MultiSelectManager.Selection;
Steve McKayd9caa6a2016-09-15 16:36:45 -070039import com.android.documentsui.roots.RootCursorWrapper;
Garfield, Tan11d23482016-08-05 09:33:29 -070040import com.android.documentsui.sorting.SortDimension;
41import com.android.documentsui.sorting.SortModel;
Ben Kwa0497da82015-11-30 23:00:02 -080042
Steve McKay990f76e2016-09-16 12:36:58 -070043import java.lang.annotation.Retention;
44import java.lang.annotation.RetentionPolicy;
Ben Kwa0497da82015-11-30 23:00:02 -080045import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080046import java.util.HashMap;
47import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080048import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080049
50/**
51 * The data model for the current loaded directory.
52 */
53@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090054public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080055 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080056
Ben Kwa0497da82015-11-30 23:00:02 -080057 private boolean mIsLoading;
Steve McKay990f76e2016-09-16 12:36:58 -070058 private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080059 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080060 private int mCursorCount;
61 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
62 private Map<String, Integer> mPositions = new HashMap<>();
63 /**
64 * A sorted array of model IDs for the files currently in the Model. Sort order is determined
Garfield, Tan11d23482016-08-05 09:33:29 -070065 * by {@link #mSortModel}
Ben Kwab8a5e082015-12-07 13:25:27 -080066 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090067 private String mIds[] = new String[0];
Garfield, Tan11d23482016-08-05 09:33:29 -070068 private SortModel mSortModel;
Ben Kwab8a5e082015-12-07 13:25:27 -080069
Ben Kwa0497da82015-11-30 23:00:02 -080070 @Nullable String info;
71 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090072 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080073
Steve McKay990f76e2016-09-16 12:36:58 -070074 public void addUpdateListener(EventListener<Update> listener) {
75 mUpdateListeners.add(listener);
76 }
77
78 public void removeUpdateListener(EventListener<Update> listener) {
79 mUpdateListeners.remove(listener);
80 }
81
Ben Kwad72a1da2015-12-01 19:56:57 -080082 private void notifyUpdateListeners() {
Steve McKay990f76e2016-09-16 12:36:58 -070083 for (EventListener<Update> handler: mUpdateListeners) {
84 handler.accept(Update.UPDATE);
Ben Kwad72a1da2015-12-01 19:56:57 -080085 }
86 }
87
88 private void notifyUpdateListeners(Exception e) {
Steve McKay990f76e2016-09-16 12:36:58 -070089 Update error = new Update(e);
90 for (EventListener<Update> handler: mUpdateListeners) {
91 handler.accept(error);
Ben Kwad72a1da2015-12-01 19:56:57 -080092 }
93 }
94
Steve McKay9de0da62016-08-25 15:18:23 -070095 void onLoaderReset() {
96 if (mIsLoading) {
Steve McKay7c662092016-08-26 12:17:41 -070097 Log.w(TAG, "Received unexpected loader reset while in loading state for doc: "
98 + DocumentInfo.debugString(doc));
Ben Kwa0497da82015-11-30 23:00:02 -080099 }
Steve McKay7c662092016-08-26 12:17:41 -0700100
101 reset();
Steve McKay9de0da62016-08-25 15:18:23 -0700102 }
103
104 private void reset() {
105 mCursor = null;
106 mCursorCount = 0;
107 mIds = new String[0];
108 mPositions.clear();
109 info = null;
110 error = null;
111 doc = null;
112 mIsLoading = false;
113 notifyUpdateListeners();
114 }
115
116 void update(DirectoryResult result) {
117 assert(result != null);
118
119 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
Ben Kwa0497da82015-11-30 23:00:02 -0800120
121 if (result.exception != null) {
122 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800123 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800124 return;
125 }
126
127 mCursor = result.cursor;
128 mCursorCount = mCursor.getCount();
Garfield, Tan11d23482016-08-05 09:33:29 -0700129 mSortModel = result.sortModel;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900130 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800131
Ben Kwab8a5e082015-12-07 13:25:27 -0800132 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800133
134 final Bundle extras = mCursor.getExtras();
135 if (extras != null) {
136 info = extras.getString(DocumentsContract.EXTRA_INFO);
137 error = extras.getString(DocumentsContract.EXTRA_ERROR);
138 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
139 }
140
Ben Kwad72a1da2015-12-01 19:56:57 -0800141 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800142 }
143
Ben Kwad72a1da2015-12-01 19:56:57 -0800144 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800145 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800146 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800147 }
148
149 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800150 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
151 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800152 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800153 private void updateModelData() {
154 int[] positions = new int[mCursorCount];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900155 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900156 boolean[] isDirs = new boolean[mCursorCount];
157 String[] displayNames = null;
Ben Kwa6280de02015-12-16 19:42:08 -0800158 long[] longValues = null;
Ben Kwab8a5e082015-12-07 13:25:27 -0800159
Garfield, Tan11d23482016-08-05 09:33:29 -0700160 final int id = mSortModel.getSortedDimensionId();
161 switch (id) {
162 case SortModel.SORT_DIMENSION_ID_TITLE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900163 displayNames = new String[mCursorCount];
164 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700165 case SortModel.SORT_DIMENSION_ID_DATE:
166 case SortModel.SORT_DIMENSION_ID_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900167 longValues = new long[mCursorCount];
168 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800169 }
170
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900171 String mimeType;
172
Ben Kwa0497da82015-11-30 23:00:02 -0800173 mCursor.moveToPosition(-1);
174 for (int pos = 0; pos < mCursorCount; ++pos) {
Ben Lin2df30e52016-04-22 16:12:50 -0700175 if (!mCursor.moveToNext()) {
176 Log.e(TAG, "Fail to move cursor to next pos: " + pos);
177 return;
178 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800179 positions[pos] = pos;
Ben Kwab8a5e082015-12-07 13:25:27 -0800180
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900181 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
182 // unique string that can be used to identify the document referred to by the cursor.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900183 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
184 // with the authority to avoid collisions.
185 if (mCursor instanceof MergeCursor) {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900186 mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY) + "|" +
187 getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900188 } else {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900189 mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900190 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900191
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900192 mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900193 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
194
Garfield, Tan11d23482016-08-05 09:33:29 -0700195 switch(id) {
196 case SortModel.SORT_DIMENSION_ID_TITLE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900197 final String displayName = getCursorString(
198 mCursor, Document.COLUMN_DISPLAY_NAME);
199 displayNames[pos] = displayName;
Ben Kwab8a5e082015-12-07 13:25:27 -0800200 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700201 case SortModel.SORT_DIMENSION_ID_DATE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900202 longValues[pos] = getLastModified(mCursor);
Ben Kwab8a5e082015-12-07 13:25:27 -0800203 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700204 case SortModel.SORT_DIMENSION_ID_SIZE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900205 longValues[pos] = getCursorLong(mCursor, Document.COLUMN_SIZE);
Ben Kwab8a5e082015-12-07 13:25:27 -0800206 break;
207 }
208 }
209
Garfield, Tan11d23482016-08-05 09:33:29 -0700210 final SortDimension dimension = mSortModel.getDimensionById(id);
211 switch (id) {
212 case SortModel.SORT_DIMENSION_ID_TITLE:
213 binarySort(displayNames, isDirs, positions, mIds, dimension.getSortDirection());
Ben Kwab8a5e082015-12-07 13:25:27 -0800214 break;
Garfield, Tan11d23482016-08-05 09:33:29 -0700215 case SortModel.SORT_DIMENSION_ID_DATE:
216 case SortModel.SORT_DIMENSION_ID_SIZE:
217 binarySort(longValues, isDirs, positions, mIds, dimension.getSortDirection());
Ben Kwab8a5e082015-12-07 13:25:27 -0800218 break;
219 }
220
221 // Populate the positions.
222 mPositions.clear();
223 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900224 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800225 }
226 }
227
228 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800229 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900230 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
231 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800232 *
233 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900234 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800235 * @param positions Cursor positions to be sorted.
236 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800237 */
Garfield, Tan11d23482016-08-05 09:33:29 -0700238 private static void binarySort(
239 String[] sortKey,
240 boolean[] isDirs,
241 int[] positions,
242 String[] ids,
243 @SortDimension.SortDirection int direction) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800244 final int count = positions.length;
245 for (int start = 1; start < count; start++) {
246 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800247 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900248 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900249 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800250
251 int left = 0;
252 int right = start;
253
254 while (left < right) {
255 int mid = (left + right) >>> 1;
256
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900257 // Directories always go in front.
258 int compare = 0;
259 final boolean rhsIsDir = isDirs[mid];
260 if (pivotIsDir && !rhsIsDir) {
261 compare = -1;
262 } else if (!pivotIsDir && rhsIsDir) {
263 compare = 1;
264 } else {
265 final String lhs = pivotValue;
266 final String rhs = sortKey[mid];
Garfield, Tan11d23482016-08-05 09:33:29 -0700267 switch (direction) {
268 case SortDimension.SORT_DIRECTION_ASCENDING:
269 compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
270 break;
271 case SortDimension.SORT_DIRECTION_DESCENDING:
272 compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
273 break;
274 default:
275 throw new IllegalArgumentException(
276 "Unknown sorting direction: " + direction);
277 }
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900278 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800279
280 if (compare < 0) {
281 right = mid;
282 } else {
283 left = mid + 1;
284 }
285 }
286
287 int n = start - left;
288 switch (n) {
289 case 2:
290 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800291 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900292 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900293 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800294 case 1:
295 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800296 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900297 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900298 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800299 break;
300 default:
301 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800302 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900303 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900304 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800305 }
306
307 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800308 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900309 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900310 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800311 }
312 }
313
314 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800315 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
316 * key, and the second is an array of mime types. The rows are first bucketed by mime type
317 * (directories vs documents) and then each bucket is sorted independently in descending
318 * numerical order on the sort key. This code is based on TimSort.binarySort().
319 *
320 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900321 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800322 * @param positions Cursor positions to be sorted.
323 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800324 */
Ben Kwa6280de02015-12-16 19:42:08 -0800325 private static void binarySort(
Garfield, Tan11d23482016-08-05 09:33:29 -0700326 long[] sortKey,
327 boolean[] isDirs,
328 int[] positions,
329 String[] ids,
330 @SortDimension.SortDirection int direction) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800331 final int count = positions.length;
332 for (int start = 1; start < count; start++) {
333 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800334 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900335 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900336 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800337
338 int left = 0;
339 int right = start;
340
341 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800342 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800343
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900344 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800345 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900346 final boolean rhsIsDir = isDirs[mid];
347 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800348 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900349 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800350 compare = 1;
351 } else {
352 final long lhs = pivotValue;
353 final long rhs = sortKey[mid];
Garfield, Tan11d23482016-08-05 09:33:29 -0700354 switch (direction) {
355 case SortDimension.SORT_DIRECTION_ASCENDING:
356 compare = Long.compare(lhs, rhs);
357 break;
358 case SortDimension.SORT_DIRECTION_DESCENDING:
359 compare = -Long.compare(lhs, rhs);
360 break;
361 default:
362 throw new IllegalArgumentException(
363 "Unknown sorting direction: " + direction);
364 }
Ben Kwa6280de02015-12-16 19:42:08 -0800365 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800366
Ben Kwae4338342016-01-08 15:34:47 -0800367 // If numerical comparison yields a tie, use document ID as a tie breaker. This
368 // will yield stable results even if incoming items are continually shuffling and
369 // have identical numerical sort keys. One common example of this scenario is seen
370 // when sorting a set of active downloads by mod time.
371 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900372 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800373 }
374
Ben Kwab8a5e082015-12-07 13:25:27 -0800375 if (compare < 0) {
376 right = mid;
377 } else {
378 left = mid + 1;
379 }
380 }
381
382 int n = start - left;
383 switch (n) {
384 case 2:
385 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800386 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900387 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900388 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800389 case 1:
390 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800391 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900392 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900393 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800394 break;
395 default:
396 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800397 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900398 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900399 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800400 }
401
402 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800403 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900404 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900405 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800406 }
407 }
408
Ben Kwae4338342016-01-08 15:34:47 -0800409 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900410 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900411 * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
Garfield, Tan11d23482016-08-05 09:33:29 -0700412 * when sorting descending by date.
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900413 */
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900414 long getLastModified(Cursor cursor) {
415 long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
416 return (l == -1) ? Long.MAX_VALUE : l;
Ben Kwae4338342016-01-08 15:34:47 -0800417 }
418
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900419 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800420 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700421 if (pos == null) {
422 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
423 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800424 }
Steve McKay5a22a112016-04-12 11:29:10 -0700425
426 if (!mCursor.moveToPosition(pos)) {
427 if (DEBUG) Log.d(TAG,
428 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
429 return null;
430 }
431
432 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800433 }
434
Steve McKay990f76e2016-09-16 12:36:58 -0700435 public boolean isEmpty() {
Ben Kwa0497da82015-11-30 23:00:02 -0800436 return mCursorCount == 0;
437 }
438
439 boolean isLoading() {
440 return mIsLoading;
441 }
442
Steve McKayc8889af2016-09-23 11:22:41 -0700443 public List<DocumentInfo> getDocuments(Selection selection) {
Steve McKay84769b82016-06-09 10:46:07 -0700444 final int size = (selection != null) ? selection.size() : 0;
Ben Kwa0497da82015-11-30 23:00:02 -0800445
446 final List<DocumentInfo> docs = new ArrayList<>(size);
Steve McKay84769b82016-06-09 10:46:07 -0700447 // NOTE: That as this now iterates over only final (non-provisional) selection.
448 for (String modelId: selection) {
Steve McKay990f76e2016-09-16 12:36:58 -0700449 DocumentInfo doc = getDocument(modelId);
450 if (doc == null) {
451 Log.w(TAG, "Unable to obtain document for modelId: " + modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700452 continue;
453 }
Steve McKay990f76e2016-09-16 12:36:58 -0700454 docs.add(doc);
Ben Kwa0497da82015-11-30 23:00:02 -0800455 }
456 return docs;
457 }
458
Steve McKay990f76e2016-09-16 12:36:58 -0700459 public @Nullable DocumentInfo getDocument(String modelId) {
460 final Cursor cursor = getItem(modelId);
461 return (cursor == null)
462 ? null
463 : DocumentInfo.fromDirectoryCursor(cursor);
464 }
465
Steve McKay84769b82016-06-09 10:46:07 -0700466 public Uri getItemUri(String modelId) {
467 final Cursor cursor = getItem(modelId);
468 return DocumentInfo.getUri(cursor);
469 }
470
Garfield, Tan11d23482016-08-05 09:33:29 -0700471 /**
472 * @return An ordered array of model IDs representing the documents in the model. It is sorted
473 * according to the current sort order, which was set by the last model update.
474 */
475 public String[] getModelIds() {
476 return mIds;
477 }
478
Steve McKay990f76e2016-09-16 12:36:58 -0700479 public static class Update {
Ben Kwa0497da82015-11-30 23:00:02 -0800480
Steve McKay990f76e2016-09-16 12:36:58 -0700481 public static final Update UPDATE = new Update();
Ben Kwa472103f2016-02-10 15:48:25 -0800482
Steve McKay990f76e2016-09-16 12:36:58 -0700483 @IntDef(value = {
484 TYPE_UPDATE,
485 TYPE_UPDATE_ERROR
486 })
487 @Retention(RetentionPolicy.SOURCE)
488 public @interface UpdateType {}
489 public static final int TYPE_UPDATE = 0;
490 public static final int TYPE_UPDATE_ERROR = 1;
Ben Kwa0497da82015-11-30 23:00:02 -0800491
Steve McKay990f76e2016-09-16 12:36:58 -0700492 private final @UpdateType int mType;
493 private final @Nullable Exception mException;
494
495 private Update() {
496 mType = TYPE_UPDATE;
497 mException = null;
498 }
499
500 public Update(Exception exception) {
501 assert(exception != null);
502 mType = TYPE_UPDATE_ERROR;
503 mException = exception;
504 }
505
506 public boolean isUpdate() {
507 return mType == TYPE_UPDATE;
508 }
509
510 public boolean hasError() {
511 return mType == TYPE_UPDATE_ERROR;
512 }
513
514 public @Nullable Exception getError() {
515 return mException;
516 }
Ben Kwa0497da82015-11-30 23:00:02 -0800517 }
Ben Kwa0497da82015-11-30 23:00:02 -0800518}