blob: e2a28ad9a782a040d06748fde8e93818acc3971e [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;
Steve McKay5a22a112016-04-12 11:29:10 -070040import java.util.Collections;
Ben Kwa0497da82015-11-30 23:00:02 -080041import java.util.HashMap;
42import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080043import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080044
45/**
46 * The data model for the current loaded directory.
47 */
48@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090049public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080050 private static final String TAG = "Model";
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090051 private static final String EMPTY = "";
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
61 * by {@link #mSortOrder}
62 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090063 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080064 private int mSortOrder = SORT_ORDER_DISPLAY_NAME;
65
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +090066 private int mAuthorityIndex = -1;
67 private int mDocIdIndex = -1;
68 private int mMimeTypeIndex = -1;
69 private int mDisplayNameIndex = -1;
70 private int mSizeIndex = -1;
71 private int mLastModifiedIndex = -1;
72
Ben Kwa0497da82015-11-30 23:00:02 -080073 @Nullable String info;
74 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090075 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080076
Ben Kwad72a1da2015-12-01 19:56:57 -080077 private void notifyUpdateListeners() {
78 for (UpdateListener listener: mUpdateListeners) {
79 listener.onModelUpdate(this);
80 }
81 }
82
83 private void notifyUpdateListeners(Exception e) {
84 for (UpdateListener listener: mUpdateListeners) {
85 listener.onModelUpdateFailed(e);
86 }
87 }
88
Ben Kwa0497da82015-11-30 23:00:02 -080089 void update(DirectoryResult result) {
90 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
91
92 if (result == null) {
93 mCursor = null;
94 mCursorCount = 0;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090095 mIds = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080096 mPositions.clear();
Ben Kwa0497da82015-11-30 23:00:02 -080097 info = null;
98 error = null;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090099 doc = null;
Ben Kwa0497da82015-11-30 23:00:02 -0800100 mIsLoading = false;
Ben Kwad72a1da2015-12-01 19:56:57 -0800101 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800102 return;
103 }
104
105 if (result.exception != null) {
106 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800107 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800108 return;
109 }
110
111 mCursor = result.cursor;
112 mCursorCount = mCursor.getCount();
Ben Kwab8a5e082015-12-07 13:25:27 -0800113 mSortOrder = result.sortOrder;
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900114 mAuthorityIndex = mCursor.getColumnIndex(RootCursorWrapper.COLUMN_AUTHORITY);
115 assert(mAuthorityIndex != -1);
116 mDocIdIndex = mCursor.getColumnIndex(Document.COLUMN_DOCUMENT_ID);
117 mMimeTypeIndex = mCursor.getColumnIndex(Document.COLUMN_MIME_TYPE);
118 mDisplayNameIndex = mCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
119 mLastModifiedIndex = mCursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
120 mSizeIndex = mCursor.getColumnIndex(Document.COLUMN_SIZE);
121
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900122 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800123
Ben Kwab8a5e082015-12-07 13:25:27 -0800124 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800125
126 final Bundle extras = mCursor.getExtras();
127 if (extras != null) {
128 info = extras.getString(DocumentsContract.EXTRA_INFO);
129 error = extras.getString(DocumentsContract.EXTRA_ERROR);
130 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
131 }
132
Ben Kwad72a1da2015-12-01 19:56:57 -0800133 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800134 }
135
Ben Kwad72a1da2015-12-01 19:56:57 -0800136 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800137 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800138 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800139 }
140
141 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800142 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
143 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800144 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800145 private void updateModelData() {
146 int[] positions = new int[mCursorCount];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900147 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900148 boolean[] isDirs = new boolean[mCursorCount];
149 String[] displayNames = null;
Ben Kwa6280de02015-12-16 19:42:08 -0800150 long[] longValues = null;
Ben Kwab8a5e082015-12-07 13:25:27 -0800151
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900152 switch (mSortOrder) {
153 case SORT_ORDER_DISPLAY_NAME:
154 displayNames = new String[mCursorCount];
155 break;
156 case SORT_ORDER_LAST_MODIFIED:
157 case SORT_ORDER_SIZE:
158 longValues = new long[mCursorCount];
159 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800160 }
161
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900162 String mimeType;
163
Ben Kwa0497da82015-11-30 23:00:02 -0800164 mCursor.moveToPosition(-1);
165 for (int pos = 0; pos < mCursorCount; ++pos) {
166 mCursor.moveToNext();
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) {
174 mIds[pos] = getStringOrEmpty(mAuthorityIndex) + "|" + getStringOrEmpty(mDocIdIndex);
175 } else {
176 mIds[pos] = getStringOrEmpty(mDocIdIndex);
177 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900178
179 mimeType = getStringOrEmpty(mMimeTypeIndex);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900180 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
181
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900182 switch (mSortOrder) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800183 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900184 displayNames[pos] = getStringOrEmpty(mDisplayNameIndex);
Ben Kwab8a5e082015-12-07 13:25:27 -0800185 break;
186 case SORT_ORDER_LAST_MODIFIED:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900187 longValues[pos] = getLastModified();
Ben Kwab8a5e082015-12-07 13:25:27 -0800188 break;
189 case SORT_ORDER_SIZE:
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900190 longValues[pos] = getDocSize();
Ben Kwab8a5e082015-12-07 13:25:27 -0800191 break;
192 }
193 }
194
195 switch (mSortOrder) {
196 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900197 binarySort(displayNames, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800198 break;
199 case SORT_ORDER_LAST_MODIFIED:
200 case SORT_ORDER_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900201 binarySort(longValues, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800202 break;
203 }
204
205 // Populate the positions.
206 mPositions.clear();
207 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900208 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800209 }
210 }
211
212 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800213 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900214 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
215 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800216 *
217 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900218 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800219 * @param positions Cursor positions to be sorted.
220 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800221 */
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900222 private static void binarySort(String[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800223 final int count = positions.length;
224 for (int start = 1; start < count; start++) {
225 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800226 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900227 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900228 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800229
230 int left = 0;
231 int right = start;
232
233 while (left < right) {
234 int mid = (left + right) >>> 1;
235
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900236 // Directories always go in front.
237 int compare = 0;
238 final boolean rhsIsDir = isDirs[mid];
239 if (pivotIsDir && !rhsIsDir) {
240 compare = -1;
241 } else if (!pivotIsDir && rhsIsDir) {
242 compare = 1;
243 } else {
244 final String lhs = pivotValue;
245 final String rhs = sortKey[mid];
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900246 compare = Shared.compareToIgnoreCase(lhs, rhs);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900247 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800248
249 if (compare < 0) {
250 right = mid;
251 } else {
252 left = mid + 1;
253 }
254 }
255
256 int n = start - left;
257 switch (n) {
258 case 2:
259 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800260 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900261 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900262 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800263 case 1:
264 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800265 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900266 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900267 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800268 break;
269 default:
270 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800271 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900272 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900273 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800274 }
275
276 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800277 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900278 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900279 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800280 }
281 }
282
283 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800284 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
285 * key, and the second is an array of mime types. The rows are first bucketed by mime type
286 * (directories vs documents) and then each bucket is sorted independently in descending
287 * numerical order on the sort key. This code is based on TimSort.binarySort().
288 *
289 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900290 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800291 * @param positions Cursor positions to be sorted.
292 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800293 */
Ben Kwa6280de02015-12-16 19:42:08 -0800294 private static void binarySort(
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900295 long[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800296 final int count = positions.length;
297 for (int start = 1; start < count; start++) {
298 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800299 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900300 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900301 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800302
303 int left = 0;
304 int right = start;
305
306 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800307 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800308
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900309 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800310 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900311 final boolean rhsIsDir = isDirs[mid];
312 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800313 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900314 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800315 compare = 1;
316 } else {
317 final long lhs = pivotValue;
318 final long rhs = sortKey[mid];
Ben Kwae4338342016-01-08 15:34:47 -0800319 // Sort in descending numerical order. This matches legacy behaviour, which
320 // yields largest or most recent items on top.
Ben Kwa6280de02015-12-16 19:42:08 -0800321 compare = -Long.compare(lhs, rhs);
322 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800323
Ben Kwae4338342016-01-08 15:34:47 -0800324 // If numerical comparison yields a tie, use document ID as a tie breaker. This
325 // will yield stable results even if incoming items are continually shuffling and
326 // have identical numerical sort keys. One common example of this scenario is seen
327 // when sorting a set of active downloads by mod time.
328 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900329 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800330 }
331
Ben Kwab8a5e082015-12-07 13:25:27 -0800332 if (compare < 0) {
333 right = mid;
334 } else {
335 left = mid + 1;
336 }
337 }
338
339 int n = start - left;
340 switch (n) {
341 case 2:
342 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800343 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900344 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900345 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800346 case 1:
347 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800348 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900349 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900350 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800351 break;
352 default:
353 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800354 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900355 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900356 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800357 }
358
359 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800360 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900361 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900362 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800363 }
364 }
365
Ben Kwae4338342016-01-08 15:34:47 -0800366 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900367 * @return Value of the string column, or an empty string if no value, or empty value.
Ben Kwae4338342016-01-08 15:34:47 -0800368 */
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900369 private String getStringOrEmpty(int columnIndex) {
370 if (columnIndex == -1)
371 return EMPTY;
372 final String result = mCursor.getString(columnIndex);
373 return result != null ? result : EMPTY;
374 }
375
376 /**
377 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
378 * or missing timestamp - these will be replaced with MAX_LONG so that such files get sorted to
379 * the top when sorting by date.
380 */
381 private long getLastModified() {
382 if (mLastModifiedIndex == -1)
383 return Long.MAX_VALUE;
384 try {
385 final long result = mCursor.getLong(mLastModifiedIndex);
386 return result > 0 ? result : Long.MAX_VALUE;
387 } catch (NumberFormatException e) {
388 return Long.MAX_VALUE;
389 }
390 }
391
392 /**
393 * @return Size for the given document. If the size is unknown or invalid, returns 0.
394 */
395 private long getDocSize() {
396 if (mSizeIndex == -1)
397 return 0;
398 try {
399 return mCursor.getLong(mSizeIndex);
400 } catch (NumberFormatException e) {
401 return 0;
402 }
Ben Kwae4338342016-01-08 15:34:47 -0800403 }
404
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900405 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800406 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700407 if (pos == null) {
408 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
409 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800410 }
Steve McKay5a22a112016-04-12 11:29:10 -0700411
412 if (!mCursor.moveToPosition(pos)) {
413 if (DEBUG) Log.d(TAG,
414 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
415 return null;
416 }
417
418 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800419 }
420
Ben Kwa0497da82015-11-30 23:00:02 -0800421 boolean isEmpty() {
422 return mCursorCount == 0;
423 }
424
425 boolean isLoading() {
426 return mIsLoading;
427 }
428
429 List<DocumentInfo> getDocuments(Selection items) {
430 final int size = (items != null) ? items.size() : 0;
431
432 final List<DocumentInfo> docs = new ArrayList<>(size);
Ben Kwad72a1da2015-12-01 19:56:57 -0800433 for (String modelId: items.getAll()) {
434 final Cursor cursor = getItem(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700435 if (cursor == null) {
436 Log.w(TAG,
437 "Skipping document. Unabled to obtain cursor for modelId: " + modelId);
438 continue;
439 }
Steve McKay0af8afd2016-02-25 13:34:03 -0800440 docs.add(DocumentInfo.fromDirectoryCursor(cursor));
Ben Kwa0497da82015-11-30 23:00:02 -0800441 }
442 return docs;
443 }
444
Ben Kwa0497da82015-11-30 23:00:02 -0800445 void addUpdateListener(UpdateListener listener) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800446 mUpdateListeners.add(listener);
Ben Kwa0497da82015-11-30 23:00:02 -0800447 }
448
Ben Kwa472103f2016-02-10 15:48:25 -0800449 void removeUpdateListener(UpdateListener listener) {
450 mUpdateListeners.remove(listener);
451 }
452
Ben Kwad72a1da2015-12-01 19:56:57 -0800453 static interface UpdateListener {
Ben Kwa0497da82015-11-30 23:00:02 -0800454 /**
455 * Called when a successful update has occurred.
456 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800457 void onModelUpdate(Model model);
Ben Kwa0497da82015-11-30 23:00:02 -0800458
459 /**
460 * Called when an update has been attempted but failed.
461 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800462 void onModelUpdateFailed(Exception e);
Ben Kwa0497da82015-11-30 23:00:02 -0800463 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800464
465 /**
466 * @return An ordered array of model IDs representing the documents in the model. It is sorted
467 * according to the current sort order, which was set by the last model update.
468 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900469 public String[] getModelIds() {
Ben Kwab8a5e082015-12-07 13:25:27 -0800470 return mIds;
471 }
Ben Kwa0497da82015-11-30 23:00:02 -0800472}