blob: 3642b01bef59c335f989844db2c4e462a401364c [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;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090025import android.database.MergeCursor;
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;
Ben Kwa0497da82015-11-30 23:00:02 -080036import com.android.documentsui.dirlist.MultiSelectManager.Selection;
37import com.android.documentsui.model.DocumentInfo;
Ben Kwa0497da82015-11-30 23:00:02 -080038
39import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080040import java.util.HashMap;
41import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080042import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080043
44/**
45 * The data model for the current loaded directory.
46 */
47@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090048public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080049 private static final String TAG = "Model";
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090050 private static final String EMPTY = "";
Ben Kwab8a5e082015-12-07 13:25:27 -080051
Ben Kwa0497da82015-11-30 23:00:02 -080052 private boolean mIsLoading;
Ben Kwad72a1da2015-12-01 19:56:57 -080053 private List<UpdateListener> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080054 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080055 private int mCursorCount;
56 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
57 private Map<String, Integer> mPositions = new HashMap<>();
58 /**
59 * A sorted array of model IDs for the files currently in the Model. Sort order is determined
60 * by {@link #mSortOrder}
61 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090062 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080063 private int mSortOrder = SORT_ORDER_DISPLAY_NAME;
64
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090065 private int mAuthorityIndex = -1;
66 private int mDocIdIndex = -1;
67 private int mMimeTypeIndex = -1;
68 private int mDisplayNameIndex = -1;
69 private int mSizeIndex = -1;
70 private int mLastModifiedIndex = -1;
71
Ben Kwa0497da82015-11-30 23:00:02 -080072 @Nullable String info;
73 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090074 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080075
Ben Kwad72a1da2015-12-01 19:56:57 -080076 private void notifyUpdateListeners() {
77 for (UpdateListener listener: mUpdateListeners) {
78 listener.onModelUpdate(this);
79 }
80 }
81
82 private void notifyUpdateListeners(Exception e) {
83 for (UpdateListener listener: mUpdateListeners) {
84 listener.onModelUpdateFailed(e);
85 }
86 }
87
Ben Kwa0497da82015-11-30 23:00:02 -080088 void update(DirectoryResult result) {
89 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
90
91 if (result == null) {
92 mCursor = null;
93 mCursorCount = 0;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090094 mIds = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080095 mPositions.clear();
Ben Kwa0497da82015-11-30 23:00:02 -080096 info = null;
97 error = null;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090098 doc = null;
Ben Kwa0497da82015-11-30 23:00:02 -080099 mIsLoading = false;
Ben Kwad72a1da2015-12-01 19:56:57 -0800100 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800101 return;
102 }
103
104 if (result.exception != null) {
105 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800106 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800107 return;
108 }
109
110 mCursor = result.cursor;
111 mCursorCount = mCursor.getCount();
Ben Kwab8a5e082015-12-07 13:25:27 -0800112 mSortOrder = result.sortOrder;
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900113 mAuthorityIndex = mCursor.getColumnIndex(RootCursorWrapper.COLUMN_AUTHORITY);
114 assert(mAuthorityIndex != -1);
115 mDocIdIndex = mCursor.getColumnIndex(Document.COLUMN_DOCUMENT_ID);
116 mMimeTypeIndex = mCursor.getColumnIndex(Document.COLUMN_MIME_TYPE);
117 mDisplayNameIndex = mCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
118 mLastModifiedIndex = mCursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
119 mSizeIndex = mCursor.getColumnIndex(Document.COLUMN_SIZE);
120
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900121 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800122
Ben Kwab8a5e082015-12-07 13:25:27 -0800123 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800124
125 final Bundle extras = mCursor.getExtras();
126 if (extras != null) {
127 info = extras.getString(DocumentsContract.EXTRA_INFO);
128 error = extras.getString(DocumentsContract.EXTRA_ERROR);
129 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
130 }
131
Ben Kwad72a1da2015-12-01 19:56:57 -0800132 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800133 }
134
Ben Kwad72a1da2015-12-01 19:56:57 -0800135 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800136 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800137 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800138 }
139
140 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800141 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
142 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800143 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800144 private void updateModelData() {
145 int[] positions = new int[mCursorCount];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900146 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900147 boolean[] isDirs = new boolean[mCursorCount];
148 String[] displayNames = null;
Ben Kwa6280de02015-12-16 19:42:08 -0800149 long[] longValues = null;
Ben Kwab8a5e082015-12-07 13:25:27 -0800150
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900151 switch (mSortOrder) {
152 case SORT_ORDER_DISPLAY_NAME:
153 displayNames = new String[mCursorCount];
154 break;
155 case SORT_ORDER_LAST_MODIFIED:
156 case SORT_ORDER_SIZE:
157 longValues = new long[mCursorCount];
158 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800159 }
160
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900161 String mimeType;
162
Ben Kwa0497da82015-11-30 23:00:02 -0800163 mCursor.moveToPosition(-1);
164 for (int pos = 0; pos < mCursorCount; ++pos) {
165 mCursor.moveToNext();
Ben Kwab8a5e082015-12-07 13:25:27 -0800166 positions[pos] = pos;
Ben Kwab8a5e082015-12-07 13:25:27 -0800167
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900168 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
169 // unique string that can be used to identify the document referred to by the cursor.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900170 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
171 // with the authority to avoid collisions.
172 if (mCursor instanceof MergeCursor) {
173 mIds[pos] = getStringOrEmpty(mAuthorityIndex) + "|" + getStringOrEmpty(mDocIdIndex);
174 } else {
175 mIds[pos] = getStringOrEmpty(mDocIdIndex);
176 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900177
178 mimeType = getStringOrEmpty(mMimeTypeIndex);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900179 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
180
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900181 switch (mSortOrder) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800182 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900183 displayNames[pos] = getStringOrEmpty(mDisplayNameIndex);
Ben Kwab8a5e082015-12-07 13:25:27 -0800184 break;
185 case SORT_ORDER_LAST_MODIFIED:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900186 longValues[pos] = getLastModified();
Ben Kwab8a5e082015-12-07 13:25:27 -0800187 break;
188 case SORT_ORDER_SIZE:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900189 longValues[pos] = getDocSize();
Ben Kwab8a5e082015-12-07 13:25:27 -0800190 break;
191 }
192 }
193
194 switch (mSortOrder) {
195 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900196 binarySort(displayNames, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800197 break;
198 case SORT_ORDER_LAST_MODIFIED:
199 case SORT_ORDER_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900200 binarySort(longValues, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800201 break;
202 }
203
204 // Populate the positions.
205 mPositions.clear();
206 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900207 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800208 }
209 }
210
211 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800212 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900213 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
214 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800215 *
216 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900217 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800218 * @param positions Cursor positions to be sorted.
219 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800220 */
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900221 private static void binarySort(String[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800222 final int count = positions.length;
223 for (int start = 1; start < count; start++) {
224 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800225 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900226 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900227 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800228
229 int left = 0;
230 int right = start;
231
232 while (left < right) {
233 int mid = (left + right) >>> 1;
234
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900235 // Directories always go in front.
236 int compare = 0;
237 final boolean rhsIsDir = isDirs[mid];
238 if (pivotIsDir && !rhsIsDir) {
239 compare = -1;
240 } else if (!pivotIsDir && rhsIsDir) {
241 compare = 1;
242 } else {
243 final String lhs = pivotValue;
244 final String rhs = sortKey[mid];
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900245 compare = Shared.compareToIgnoreCase(lhs, rhs);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900246 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800247
248 if (compare < 0) {
249 right = mid;
250 } else {
251 left = mid + 1;
252 }
253 }
254
255 int n = start - left;
256 switch (n) {
257 case 2:
258 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800259 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900260 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900261 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800262 case 1:
263 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800264 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900265 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900266 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800267 break;
268 default:
269 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800270 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900271 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900272 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800273 }
274
275 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800276 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900277 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900278 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800279 }
280 }
281
282 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800283 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
284 * key, and the second is an array of mime types. The rows are first bucketed by mime type
285 * (directories vs documents) and then each bucket is sorted independently in descending
286 * numerical order on the sort key. This code is based on TimSort.binarySort().
287 *
288 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900289 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800290 * @param positions Cursor positions to be sorted.
291 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800292 */
Ben Kwa6280de02015-12-16 19:42:08 -0800293 private static void binarySort(
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900294 long[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800295 final int count = positions.length;
296 for (int start = 1; start < count; start++) {
297 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800298 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900299 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900300 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800301
302 int left = 0;
303 int right = start;
304
305 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800306 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800307
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900308 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800309 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900310 final boolean rhsIsDir = isDirs[mid];
311 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800312 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900313 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800314 compare = 1;
315 } else {
316 final long lhs = pivotValue;
317 final long rhs = sortKey[mid];
Ben Kwae4338342016-01-08 15:34:47 -0800318 // Sort in descending numerical order. This matches legacy behaviour, which
319 // yields largest or most recent items on top.
Ben Kwa6280de02015-12-16 19:42:08 -0800320 compare = -Long.compare(lhs, rhs);
321 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800322
Ben Kwae4338342016-01-08 15:34:47 -0800323 // If numerical comparison yields a tie, use document ID as a tie breaker. This
324 // will yield stable results even if incoming items are continually shuffling and
325 // have identical numerical sort keys. One common example of this scenario is seen
326 // when sorting a set of active downloads by mod time.
327 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900328 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800329 }
330
Ben Kwab8a5e082015-12-07 13:25:27 -0800331 if (compare < 0) {
332 right = mid;
333 } else {
334 left = mid + 1;
335 }
336 }
337
338 int n = start - left;
339 switch (n) {
340 case 2:
341 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800342 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900343 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900344 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800345 case 1:
346 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800347 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900348 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900349 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800350 break;
351 default:
352 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800353 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900354 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900355 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800356 }
357
358 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800359 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900360 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900361 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800362 }
363 }
364
Ben Kwae4338342016-01-08 15:34:47 -0800365 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900366 * @return Value of the string column, or an empty string if no value, or empty value.
Ben Kwae4338342016-01-08 15:34:47 -0800367 */
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900368 private String getStringOrEmpty(int columnIndex) {
369 if (columnIndex == -1)
370 return EMPTY;
371 final String result = mCursor.getString(columnIndex);
372 return result != null ? result : EMPTY;
373 }
374
375 /**
376 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
377 * or missing timestamp - these will be replaced with MAX_LONG so that such files get sorted to
378 * the top when sorting by date.
379 */
380 private long getLastModified() {
381 if (mLastModifiedIndex == -1)
382 return Long.MAX_VALUE;
383 try {
384 final long result = mCursor.getLong(mLastModifiedIndex);
385 return result > 0 ? result : Long.MAX_VALUE;
386 } catch (NumberFormatException e) {
387 return Long.MAX_VALUE;
388 }
389 }
390
391 /**
392 * @return Size for the given document. If the size is unknown or invalid, returns 0.
393 */
394 private long getDocSize() {
395 if (mSizeIndex == -1)
396 return 0;
397 try {
398 return mCursor.getLong(mSizeIndex);
399 } catch (NumberFormatException e) {
400 return 0;
401 }
Ben Kwae4338342016-01-08 15:34:47 -0800402 }
403
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900404 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800405 Integer pos = mPositions.get(modelId);
406 if (pos != null) {
407 mCursor.moveToPosition(pos);
408 return mCursor;
409 }
410 return null;
411 }
412
Ben Kwa0497da82015-11-30 23:00:02 -0800413 boolean isEmpty() {
414 return mCursorCount == 0;
415 }
416
417 boolean isLoading() {
418 return mIsLoading;
419 }
420
421 List<DocumentInfo> getDocuments(Selection items) {
422 final int size = (items != null) ? items.size() : 0;
423
424 final List<DocumentInfo> docs = new ArrayList<>(size);
Ben Kwad72a1da2015-12-01 19:56:57 -0800425 for (String modelId: items.getAll()) {
426 final Cursor cursor = getItem(modelId);
Steve McKay0af8afd2016-02-25 13:34:03 -0800427 assert(cursor != null);
428
429 docs.add(DocumentInfo.fromDirectoryCursor(cursor));
Ben Kwa0497da82015-11-30 23:00:02 -0800430 }
431 return docs;
432 }
433
Ben Kwa0497da82015-11-30 23:00:02 -0800434 void addUpdateListener(UpdateListener listener) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800435 mUpdateListeners.add(listener);
Ben Kwa0497da82015-11-30 23:00:02 -0800436 }
437
Ben Kwa472103f2016-02-10 15:48:25 -0800438 void removeUpdateListener(UpdateListener listener) {
439 mUpdateListeners.remove(listener);
440 }
441
Ben Kwad72a1da2015-12-01 19:56:57 -0800442 static interface UpdateListener {
Ben Kwa0497da82015-11-30 23:00:02 -0800443 /**
444 * Called when a successful update has occurred.
445 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800446 void onModelUpdate(Model model);
Ben Kwa0497da82015-11-30 23:00:02 -0800447
448 /**
449 * Called when an update has been attempted but failed.
450 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800451 void onModelUpdateFailed(Exception e);
Ben Kwa0497da82015-11-30 23:00:02 -0800452 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800453
454 /**
455 * @return An ordered array of model IDs representing the documents in the model. It is sorted
456 * according to the current sort order, which was set by the last model update.
457 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900458 public String[] getModelIds() {
Ben Kwab8a5e082015-12-07 13:25:27 -0800459 return mIds;
460 }
Ben Kwa0497da82015-11-30 23:00:02 -0800461}