blob: 179cb9bd46e328706b7d679565f882e175ecb26d [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;
Ben Kwab8a5e082015-12-07 13:25:27 -080020import static com.android.documentsui.State.SORT_ORDER_DISPLAY_NAME;
21import static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED;
22import static com.android.documentsui.State.SORT_ORDER_SIZE;
Ben Kwa0497da82015-11-30 23:00:02 -080023
Ben Kwa0497da82015-11-30 23:00:02 -080024import android.database.Cursor;
Ben Kwa0497da82015-11-30 23:00:02 -080025import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080026import android.provider.DocumentsContract;
27import android.provider.DocumentsContract.Document;
28import android.support.annotation.Nullable;
29import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080030import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080031
Ben Kwa0497da82015-11-30 23:00:02 -080032import com.android.documentsui.DirectoryResult;
Ben Kwa0497da82015-11-30 23:00:02 -080033import com.android.documentsui.RootCursorWrapper;
Steve McKay55c00e72016-02-18 15:32:16 -080034import com.android.documentsui.Shared;
Ben Kwa0497da82015-11-30 23:00:02 -080035import com.android.documentsui.dirlist.MultiSelectManager.Selection;
36import com.android.documentsui.model.DocumentInfo;
Ben Kwa0497da82015-11-30 23:00:02 -080037
38import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080039import java.util.HashMap;
40import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080041import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080042
43/**
44 * The data model for the current loaded directory.
45 */
46@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090047public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080048 private static final String TAG = "Model";
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090049 private static final String EMPTY = "";
Ben Kwab8a5e082015-12-07 13:25:27 -080050
Ben Kwa0497da82015-11-30 23:00:02 -080051 private boolean mIsLoading;
Ben Kwad72a1da2015-12-01 19:56:57 -080052 private List<UpdateListener> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080053 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080054 private int mCursorCount;
55 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
56 private Map<String, Integer> mPositions = new HashMap<>();
57 /**
58 * A sorted array of model IDs for the files currently in the Model. Sort order is determined
59 * by {@link #mSortOrder}
60 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090061 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080062 private int mSortOrder = SORT_ORDER_DISPLAY_NAME;
63
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090064 private int mAuthorityIndex = -1;
65 private int mDocIdIndex = -1;
66 private int mMimeTypeIndex = -1;
67 private int mDisplayNameIndex = -1;
68 private int mSizeIndex = -1;
69 private int mLastModifiedIndex = -1;
70
Ben Kwa0497da82015-11-30 23:00:02 -080071 @Nullable String info;
72 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090073 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080074
Aga Wronska35a60a72016-03-16 13:44:27 -070075 /**
76 * Generates a Model ID for a cursor entry that refers to a document. The Model ID is a unique
77 * string that can be used to identify the document referred to by the cursor.
78 *
79 * @param c A cursor that refers to a document.
80 */
Aga Wronska35a60a72016-03-16 13:44:27 -070081 static String createModelId(String authority, String docId) {
82 return authority + "|" + docId;
83 }
84
Ben Kwad72a1da2015-12-01 19:56:57 -080085 private void notifyUpdateListeners() {
86 for (UpdateListener listener: mUpdateListeners) {
87 listener.onModelUpdate(this);
88 }
89 }
90
91 private void notifyUpdateListeners(Exception e) {
92 for (UpdateListener listener: mUpdateListeners) {
93 listener.onModelUpdateFailed(e);
94 }
95 }
96
Ben Kwa0497da82015-11-30 23:00:02 -080097 void update(DirectoryResult result) {
98 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
99
100 if (result == null) {
101 mCursor = null;
102 mCursorCount = 0;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900103 mIds = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -0800104 mPositions.clear();
Ben Kwa0497da82015-11-30 23:00:02 -0800105 info = null;
106 error = null;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900107 doc = null;
Ben Kwa0497da82015-11-30 23:00:02 -0800108 mIsLoading = false;
Ben Kwad72a1da2015-12-01 19:56:57 -0800109 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800110 return;
111 }
112
113 if (result.exception != null) {
114 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800115 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800116 return;
117 }
118
119 mCursor = result.cursor;
120 mCursorCount = mCursor.getCount();
Ben Kwab8a5e082015-12-07 13:25:27 -0800121 mSortOrder = result.sortOrder;
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900122 mAuthorityIndex = mCursor.getColumnIndex(RootCursorWrapper.COLUMN_AUTHORITY);
123 assert(mAuthorityIndex != -1);
124 mDocIdIndex = mCursor.getColumnIndex(Document.COLUMN_DOCUMENT_ID);
125 mMimeTypeIndex = mCursor.getColumnIndex(Document.COLUMN_MIME_TYPE);
126 mDisplayNameIndex = mCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
127 mLastModifiedIndex = mCursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
128 mSizeIndex = mCursor.getColumnIndex(Document.COLUMN_SIZE);
129
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
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900160 switch (mSortOrder) {
161 case SORT_ORDER_DISPLAY_NAME:
162 displayNames = new String[mCursorCount];
163 break;
164 case SORT_ORDER_LAST_MODIFIED:
165 case SORT_ORDER_SIZE:
166 longValues = new long[mCursorCount];
167 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800168 }
169
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900170 String mimeType;
171
Ben Kwa0497da82015-11-30 23:00:02 -0800172 mCursor.moveToPosition(-1);
173 for (int pos = 0; pos < mCursorCount; ++pos) {
174 mCursor.moveToNext();
Ben Kwab8a5e082015-12-07 13:25:27 -0800175 positions[pos] = pos;
Ben Kwab8a5e082015-12-07 13:25:27 -0800176
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900177 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
178 // unique string that can be used to identify the document referred to by the cursor.
179 mIds[pos] = createModelId(
180 getStringOrEmpty(mAuthorityIndex), getStringOrEmpty(mDocIdIndex));
181
182 mimeType = getStringOrEmpty(mMimeTypeIndex);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900183 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
184
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900185 switch (mSortOrder) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800186 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900187 displayNames[pos] = getStringOrEmpty(mDisplayNameIndex);
Ben Kwab8a5e082015-12-07 13:25:27 -0800188 break;
189 case SORT_ORDER_LAST_MODIFIED:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900190 longValues[pos] = getLastModified();
Ben Kwab8a5e082015-12-07 13:25:27 -0800191 break;
192 case SORT_ORDER_SIZE:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900193 longValues[pos] = getDocSize();
Ben Kwab8a5e082015-12-07 13:25:27 -0800194 break;
195 }
196 }
197
198 switch (mSortOrder) {
199 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900200 binarySort(displayNames, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800201 break;
202 case SORT_ORDER_LAST_MODIFIED:
203 case SORT_ORDER_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900204 binarySort(longValues, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800205 break;
206 }
207
208 // Populate the positions.
209 mPositions.clear();
210 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900211 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800212 }
213 }
214
215 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800216 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900217 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
218 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800219 *
220 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900221 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800222 * @param positions Cursor positions to be sorted.
223 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800224 */
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900225 private static void binarySort(String[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800226 final int count = positions.length;
227 for (int start = 1; start < count; start++) {
228 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800229 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900230 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900231 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800232
233 int left = 0;
234 int right = start;
235
236 while (left < right) {
237 int mid = (left + right) >>> 1;
238
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900239 // Directories always go in front.
240 int compare = 0;
241 final boolean rhsIsDir = isDirs[mid];
242 if (pivotIsDir && !rhsIsDir) {
243 compare = -1;
244 } else if (!pivotIsDir && rhsIsDir) {
245 compare = 1;
246 } else {
247 final String lhs = pivotValue;
248 final String rhs = sortKey[mid];
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900249 compare = Shared.compareToIgnoreCase(lhs, rhs);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900250 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800251
252 if (compare < 0) {
253 right = mid;
254 } else {
255 left = mid + 1;
256 }
257 }
258
259 int n = start - left;
260 switch (n) {
261 case 2:
262 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800263 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900264 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900265 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800266 case 1:
267 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800268 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900269 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900270 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800271 break;
272 default:
273 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800274 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900275 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900276 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800277 }
278
279 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800280 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900281 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900282 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800283 }
284 }
285
286 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800287 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
288 * key, and the second is an array of mime types. The rows are first bucketed by mime type
289 * (directories vs documents) and then each bucket is sorted independently in descending
290 * numerical order on the sort key. This code is based on TimSort.binarySort().
291 *
292 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900293 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800294 * @param positions Cursor positions to be sorted.
295 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800296 */
Ben Kwa6280de02015-12-16 19:42:08 -0800297 private static void binarySort(
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900298 long[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800299 final int count = positions.length;
300 for (int start = 1; start < count; start++) {
301 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800302 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900303 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900304 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800305
306 int left = 0;
307 int right = start;
308
309 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800310 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800311
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900312 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800313 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900314 final boolean rhsIsDir = isDirs[mid];
315 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800316 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900317 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800318 compare = 1;
319 } else {
320 final long lhs = pivotValue;
321 final long rhs = sortKey[mid];
Ben Kwae4338342016-01-08 15:34:47 -0800322 // Sort in descending numerical order. This matches legacy behaviour, which
323 // yields largest or most recent items on top.
Ben Kwa6280de02015-12-16 19:42:08 -0800324 compare = -Long.compare(lhs, rhs);
325 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800326
Ben Kwae4338342016-01-08 15:34:47 -0800327 // If numerical comparison yields a tie, use document ID as a tie breaker. This
328 // will yield stable results even if incoming items are continually shuffling and
329 // have identical numerical sort keys. One common example of this scenario is seen
330 // when sorting a set of active downloads by mod time.
331 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900332 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800333 }
334
Ben Kwab8a5e082015-12-07 13:25:27 -0800335 if (compare < 0) {
336 right = mid;
337 } else {
338 left = mid + 1;
339 }
340 }
341
342 int n = start - left;
343 switch (n) {
344 case 2:
345 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800346 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900347 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900348 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800349 case 1:
350 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800351 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900352 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900353 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800354 break;
355 default:
356 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800357 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900358 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900359 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800360 }
361
362 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800363 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900364 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900365 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800366 }
367 }
368
Ben Kwae4338342016-01-08 15:34:47 -0800369 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900370 * @return Value of the string column, or an empty string if no value, or empty value.
Ben Kwae4338342016-01-08 15:34:47 -0800371 */
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900372 private String getStringOrEmpty(int columnIndex) {
373 if (columnIndex == -1)
374 return EMPTY;
375 final String result = mCursor.getString(columnIndex);
376 return result != null ? result : EMPTY;
377 }
378
379 /**
380 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
381 * or missing timestamp - these will be replaced with MAX_LONG so that such files get sorted to
382 * the top when sorting by date.
383 */
384 private long getLastModified() {
385 if (mLastModifiedIndex == -1)
386 return Long.MAX_VALUE;
387 try {
388 final long result = mCursor.getLong(mLastModifiedIndex);
389 return result > 0 ? result : Long.MAX_VALUE;
390 } catch (NumberFormatException e) {
391 return Long.MAX_VALUE;
392 }
393 }
394
395 /**
396 * @return Size for the given document. If the size is unknown or invalid, returns 0.
397 */
398 private long getDocSize() {
399 if (mSizeIndex == -1)
400 return 0;
401 try {
402 return mCursor.getLong(mSizeIndex);
403 } catch (NumberFormatException e) {
404 return 0;
405 }
Ben Kwae4338342016-01-08 15:34:47 -0800406 }
407
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900408 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800409 Integer pos = mPositions.get(modelId);
410 if (pos != null) {
411 mCursor.moveToPosition(pos);
412 return mCursor;
413 }
414 return null;
415 }
416
Ben Kwa0497da82015-11-30 23:00:02 -0800417 boolean isEmpty() {
418 return mCursorCount == 0;
419 }
420
421 boolean isLoading() {
422 return mIsLoading;
423 }
424
425 List<DocumentInfo> getDocuments(Selection items) {
426 final int size = (items != null) ? items.size() : 0;
427
428 final List<DocumentInfo> docs = new ArrayList<>(size);
Ben Kwad72a1da2015-12-01 19:56:57 -0800429 for (String modelId: items.getAll()) {
430 final Cursor cursor = getItem(modelId);
Steve McKay0af8afd2016-02-25 13:34:03 -0800431 assert(cursor != null);
432
433 docs.add(DocumentInfo.fromDirectoryCursor(cursor));
Ben Kwa0497da82015-11-30 23:00:02 -0800434 }
435 return docs;
436 }
437
Ben Kwa0497da82015-11-30 23:00:02 -0800438 void addUpdateListener(UpdateListener listener) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800439 mUpdateListeners.add(listener);
Ben Kwa0497da82015-11-30 23:00:02 -0800440 }
441
Ben Kwa472103f2016-02-10 15:48:25 -0800442 void removeUpdateListener(UpdateListener listener) {
443 mUpdateListeners.remove(listener);
444 }
445
Ben Kwad72a1da2015-12-01 19:56:57 -0800446 static interface UpdateListener {
Ben Kwa0497da82015-11-30 23:00:02 -0800447 /**
448 * Called when a successful update has occurred.
449 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800450 void onModelUpdate(Model model);
Ben Kwa0497da82015-11-30 23:00:02 -0800451
452 /**
453 * Called when an update has been attempted but failed.
454 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800455 void onModelUpdateFailed(Exception e);
Ben Kwa0497da82015-11-30 23:00:02 -0800456 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800457
458 /**
459 * @return An ordered array of model IDs representing the documents in the model. It is sorted
460 * according to the current sort order, which was set by the last model update.
461 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900462 public String[] getModelIds() {
Ben Kwab8a5e082015-12-07 13:25:27 -0800463 return mIds;
464 }
Ben Kwa0497da82015-11-30 23:00:02 -0800465}