blob: 5c1522811364a53e1627e17bb6c4b58cfef0f37a [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;
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +090023import static com.android.documentsui.model.DocumentInfo.getCursorLong;
24import static com.android.documentsui.model.DocumentInfo.getCursorString;
Ben Kwa0497da82015-11-30 23:00:02 -080025
Ben Kwa0497da82015-11-30 23:00:02 -080026import android.database.Cursor;
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +090027import android.database.MergeCursor;
Steve McKay84769b82016-06-09 10:46:07 -070028import android.net.Uri;
Ben Kwa0497da82015-11-30 23:00:02 -080029import android.os.Bundle;
Ben Kwa0497da82015-11-30 23:00:02 -080030import android.provider.DocumentsContract;
31import android.provider.DocumentsContract.Document;
32import android.support.annotation.Nullable;
33import android.support.annotation.VisibleForTesting;
Ben Kwa0497da82015-11-30 23:00:02 -080034import android.util.Log;
Ben Kwa0497da82015-11-30 23:00:02 -080035
Ben Kwa0497da82015-11-30 23:00:02 -080036import com.android.documentsui.DirectoryResult;
Ben Kwa0497da82015-11-30 23:00:02 -080037import com.android.documentsui.RootCursorWrapper;
Steve McKay55c00e72016-02-18 15:32:16 -080038import com.android.documentsui.Shared;
Ben Kwa0497da82015-11-30 23:00:02 -080039import com.android.documentsui.dirlist.MultiSelectManager.Selection;
40import com.android.documentsui.model.DocumentInfo;
Ben Kwa0497da82015-11-30 23:00:02 -080041
42import java.util.ArrayList;
Ben Kwa0497da82015-11-30 23:00:02 -080043import java.util.HashMap;
44import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080045import java.util.Map;
Ben Kwa0497da82015-11-30 23:00:02 -080046
47/**
48 * The data model for the current loaded directory.
49 */
50@VisibleForTesting
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +090051public class Model {
Ben Kwa0497da82015-11-30 23:00:02 -080052 private static final String TAG = "Model";
Ben Kwab8a5e082015-12-07 13:25:27 -080053
Ben Kwa0497da82015-11-30 23:00:02 -080054 private boolean mIsLoading;
Ben Kwad72a1da2015-12-01 19:56:57 -080055 private List<UpdateListener> mUpdateListeners = new ArrayList<>();
Ben Kwa0497da82015-11-30 23:00:02 -080056 @Nullable private Cursor mCursor;
Ben Kwab8a5e082015-12-07 13:25:27 -080057 private int mCursorCount;
58 /** Maps Model ID to cursor positions, for looking up items by Model ID. */
59 private Map<String, Integer> mPositions = new HashMap<>();
60 /**
61 * A sorted array of model IDs for the files currently in the Model. Sort order is determined
62 * by {@link #mSortOrder}
63 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090064 private String mIds[] = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080065 private int mSortOrder = SORT_ORDER_DISPLAY_NAME;
66
Ben Kwa0497da82015-11-30 23:00:02 -080067 @Nullable String info;
68 @Nullable String error;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090069 @Nullable DocumentInfo doc;
Ben Kwa0497da82015-11-30 23:00:02 -080070
Ben Kwad72a1da2015-12-01 19:56:57 -080071 private void notifyUpdateListeners() {
72 for (UpdateListener listener: mUpdateListeners) {
73 listener.onModelUpdate(this);
74 }
75 }
76
77 private void notifyUpdateListeners(Exception e) {
78 for (UpdateListener listener: mUpdateListeners) {
79 listener.onModelUpdateFailed(e);
80 }
81 }
82
Ben Kwa0497da82015-11-30 23:00:02 -080083 void update(DirectoryResult result) {
84 if (DEBUG) Log.i(TAG, "Updating model with new result set.");
85
86 if (result == null) {
87 mCursor = null;
88 mCursorCount = 0;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +090089 mIds = new String[0];
Ben Kwab8a5e082015-12-07 13:25:27 -080090 mPositions.clear();
Ben Kwa0497da82015-11-30 23:00:02 -080091 info = null;
92 error = null;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +090093 doc = null;
Ben Kwa0497da82015-11-30 23:00:02 -080094 mIsLoading = false;
Ben Kwad72a1da2015-12-01 19:56:57 -080095 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -080096 return;
97 }
98
99 if (result.exception != null) {
100 Log.e(TAG, "Error while loading directory contents", result.exception);
Ben Kwad72a1da2015-12-01 19:56:57 -0800101 notifyUpdateListeners(result.exception);
Ben Kwa0497da82015-11-30 23:00:02 -0800102 return;
103 }
104
105 mCursor = result.cursor;
106 mCursorCount = mCursor.getCount();
Ben Kwab8a5e082015-12-07 13:25:27 -0800107 mSortOrder = result.sortOrder;
Tomasz Mikolajewskie29e3412016-02-24 12:53:44 +0900108 doc = result.doc;
Ben Kwa0497da82015-11-30 23:00:02 -0800109
Ben Kwab8a5e082015-12-07 13:25:27 -0800110 updateModelData();
Ben Kwa0497da82015-11-30 23:00:02 -0800111
112 final Bundle extras = mCursor.getExtras();
113 if (extras != null) {
114 info = extras.getString(DocumentsContract.EXTRA_INFO);
115 error = extras.getString(DocumentsContract.EXTRA_ERROR);
116 mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
117 }
118
Ben Kwad72a1da2015-12-01 19:56:57 -0800119 notifyUpdateListeners();
Ben Kwa0497da82015-11-30 23:00:02 -0800120 }
121
Ben Kwad72a1da2015-12-01 19:56:57 -0800122 @VisibleForTesting
Ben Kwa0497da82015-11-30 23:00:02 -0800123 int getItemCount() {
Ben Kwada858bf2015-12-09 14:33:49 -0800124 return mCursorCount;
Ben Kwa0497da82015-11-30 23:00:02 -0800125 }
126
127 /**
Ben Kwab8a5e082015-12-07 13:25:27 -0800128 * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs
129 * according to the current sort order.
Ben Kwa0497da82015-11-30 23:00:02 -0800130 */
Ben Kwab8a5e082015-12-07 13:25:27 -0800131 private void updateModelData() {
132 int[] positions = new int[mCursorCount];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900133 mIds = new String[mCursorCount];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900134 boolean[] isDirs = new boolean[mCursorCount];
135 String[] displayNames = null;
Ben Kwa6280de02015-12-16 19:42:08 -0800136 long[] longValues = null;
Ben Kwab8a5e082015-12-07 13:25:27 -0800137
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900138 switch (mSortOrder) {
139 case SORT_ORDER_DISPLAY_NAME:
140 displayNames = new String[mCursorCount];
141 break;
142 case SORT_ORDER_LAST_MODIFIED:
143 case SORT_ORDER_SIZE:
144 longValues = new long[mCursorCount];
145 break;
Ben Kwab8a5e082015-12-07 13:25:27 -0800146 }
147
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900148 String mimeType;
149
Ben Kwa0497da82015-11-30 23:00:02 -0800150 mCursor.moveToPosition(-1);
151 for (int pos = 0; pos < mCursorCount; ++pos) {
Ben Lin2df30e52016-04-22 16:12:50 -0700152 if (!mCursor.moveToNext()) {
153 Log.e(TAG, "Fail to move cursor to next pos: " + pos);
154 return;
155 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800156 positions[pos] = pos;
Ben Kwab8a5e082015-12-07 13:25:27 -0800157
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900158 // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
159 // unique string that can be used to identify the document referred to by the cursor.
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900160 // If the cursor is a merged cursor over multiple authorities, then prefix the ids
161 // with the authority to avoid collisions.
162 if (mCursor instanceof MergeCursor) {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900163 mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY) + "|" +
164 getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900165 } else {
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900166 mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
Tomasz Mikolajewski20c04c52016-03-15 15:55:46 +0900167 }
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900168
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900169 mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900170 isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
171
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900172 switch(mSortOrder) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800173 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900174 final String displayName = getCursorString(
175 mCursor, Document.COLUMN_DISPLAY_NAME);
176 displayNames[pos] = displayName;
Ben Kwab8a5e082015-12-07 13:25:27 -0800177 break;
178 case SORT_ORDER_LAST_MODIFIED:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900179 longValues[pos] = getLastModified(mCursor);
Ben Kwab8a5e082015-12-07 13:25:27 -0800180 break;
181 case SORT_ORDER_SIZE:
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900182 longValues[pos] = getCursorLong(mCursor, Document.COLUMN_SIZE);
Ben Kwab8a5e082015-12-07 13:25:27 -0800183 break;
184 }
185 }
186
187 switch (mSortOrder) {
188 case SORT_ORDER_DISPLAY_NAME:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900189 binarySort(displayNames, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800190 break;
191 case SORT_ORDER_LAST_MODIFIED:
192 case SORT_ORDER_SIZE:
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900193 binarySort(longValues, isDirs, positions, mIds);
Ben Kwab8a5e082015-12-07 13:25:27 -0800194 break;
195 }
196
197 // Populate the positions.
198 mPositions.clear();
199 for (int i = 0; i < mCursorCount; ++i) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900200 mPositions.put(mIds[i], positions[i]);
Ben Kwab8a5e082015-12-07 13:25:27 -0800201 }
202 }
203
204 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800205 * Sorts model data. Takes three columns of index-corresponded data. The first column is the
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900206 * sort key. Rows are sorted in ascending alphabetical order on the sort key.
207 * Directories are always shown first. This code is based on TimSort.binarySort().
Ben Kwa6280de02015-12-16 19:42:08 -0800208 *
209 * @param sortKey Data is sorted in ascending alphabetical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900210 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800211 * @param positions Cursor positions to be sorted.
212 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800213 */
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900214 private static void binarySort(String[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800215 final int count = positions.length;
216 for (int start = 1; start < count; start++) {
217 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800218 final String pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900219 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900220 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800221
222 int left = 0;
223 int right = start;
224
225 while (left < right) {
226 int mid = (left + right) >>> 1;
227
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900228 // Directories always go in front.
229 int compare = 0;
230 final boolean rhsIsDir = isDirs[mid];
231 if (pivotIsDir && !rhsIsDir) {
232 compare = -1;
233 } else if (!pivotIsDir && rhsIsDir) {
234 compare = 1;
235 } else {
236 final String lhs = pivotValue;
237 final String rhs = sortKey[mid];
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900238 compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900239 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800240
241 if (compare < 0) {
242 right = mid;
243 } else {
244 left = mid + 1;
245 }
246 }
247
248 int n = start - left;
249 switch (n) {
250 case 2:
251 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800252 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900253 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900254 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800255 case 1:
256 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800257 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900258 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900259 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800260 break;
261 default:
262 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800263 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900264 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900265 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800266 }
267
268 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800269 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900270 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900271 ids[left] = pivotId;
Ben Kwab8a5e082015-12-07 13:25:27 -0800272 }
273 }
274
275 /**
Ben Kwa6280de02015-12-16 19:42:08 -0800276 * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
277 * key, and the second is an array of mime types. The rows are first bucketed by mime type
278 * (directories vs documents) and then each bucket is sorted independently in descending
279 * numerical order on the sort key. This code is based on TimSort.binarySort().
280 *
281 * @param sortKey Data is sorted in descending numerical order.
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900282 * @param isDirs Array saying whether an item is a directory or not.
Ben Kwa6280de02015-12-16 19:42:08 -0800283 * @param positions Cursor positions to be sorted.
284 * @param ids Model IDs to be sorted.
Ben Kwab8a5e082015-12-07 13:25:27 -0800285 */
Ben Kwa6280de02015-12-16 19:42:08 -0800286 private static void binarySort(
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900287 long[] sortKey, boolean[] isDirs, int[] positions, String[] ids) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800288 final int count = positions.length;
289 for (int start = 1; start < count; start++) {
290 final int pivotPosition = positions[start];
Ben Kwa6280de02015-12-16 19:42:08 -0800291 final long pivotValue = sortKey[start];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900292 final boolean pivotIsDir = isDirs[start];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900293 final String pivotId = ids[start];
Ben Kwab8a5e082015-12-07 13:25:27 -0800294
295 int left = 0;
296 int right = start;
297
298 while (left < right) {
Ben Kwa6280de02015-12-16 19:42:08 -0800299 int mid = ((left + right) >>> 1);
Ben Kwab8a5e082015-12-07 13:25:27 -0800300
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900301 // Directories always go in front.
Ben Kwa6280de02015-12-16 19:42:08 -0800302 int compare = 0;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900303 final boolean rhsIsDir = isDirs[mid];
304 if (pivotIsDir && !rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800305 compare = -1;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900306 } else if (!pivotIsDir && rhsIsDir) {
Ben Kwa6280de02015-12-16 19:42:08 -0800307 compare = 1;
308 } else {
309 final long lhs = pivotValue;
310 final long rhs = sortKey[mid];
Ben Kwae4338342016-01-08 15:34:47 -0800311 // Sort in descending numerical order. This matches legacy behaviour, which
312 // yields largest or most recent items on top.
Ben Kwa6280de02015-12-16 19:42:08 -0800313 compare = -Long.compare(lhs, rhs);
314 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800315
Ben Kwae4338342016-01-08 15:34:47 -0800316 // If numerical comparison yields a tie, use document ID as a tie breaker. This
317 // will yield stable results even if incoming items are continually shuffling and
318 // have identical numerical sort keys. One common example of this scenario is seen
319 // when sorting a set of active downloads by mod time.
320 if (compare == 0) {
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900321 compare = pivotId.compareTo(ids[mid]);
Ben Kwae4338342016-01-08 15:34:47 -0800322 }
323
Ben Kwab8a5e082015-12-07 13:25:27 -0800324 if (compare < 0) {
325 right = mid;
326 } else {
327 left = mid + 1;
328 }
329 }
330
331 int n = start - left;
332 switch (n) {
333 case 2:
334 positions[left + 2] = positions[left + 1];
Ben Kwa6280de02015-12-16 19:42:08 -0800335 sortKey[left + 2] = sortKey[left + 1];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900336 isDirs[left + 2] = isDirs[left + 1];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900337 ids[left + 2] = ids[left + 1];
Ben Kwab8a5e082015-12-07 13:25:27 -0800338 case 1:
339 positions[left + 1] = positions[left];
Ben Kwa6280de02015-12-16 19:42:08 -0800340 sortKey[left + 1] = sortKey[left];
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900341 isDirs[left + 1] = isDirs[left];
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900342 ids[left + 1] = ids[left];
Ben Kwab8a5e082015-12-07 13:25:27 -0800343 break;
344 default:
345 System.arraycopy(positions, left, positions, left + 1, n);
Ben Kwa6280de02015-12-16 19:42:08 -0800346 System.arraycopy(sortKey, left, sortKey, left + 1, n);
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900347 System.arraycopy(isDirs, left, isDirs, left + 1, n);
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900348 System.arraycopy(ids, left, ids, left + 1, n);
Ben Kwab8a5e082015-12-07 13:25:27 -0800349 }
350
351 positions[left] = pivotPosition;
Ben Kwa6280de02015-12-16 19:42:08 -0800352 sortKey[left] = pivotValue;
Tomasz Mikolajewski61e315d2016-03-15 13:23:52 +0900353 isDirs[left] = pivotIsDir;
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900354 ids[left] = pivotId;
Ben Kwa0497da82015-11-30 23:00:02 -0800355 }
356 }
357
Ben Kwae4338342016-01-08 15:34:47 -0800358 /**
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900359 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900360 * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
361 * when sorting by date.
Tomasz Mikolajewski985df3d2016-03-15 15:38:54 +0900362 */
Tomasz Mikolajewski06b036f2016-04-26 11:11:17 +0900363 long getLastModified(Cursor cursor) {
364 long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
365 return (l == -1) ? Long.MAX_VALUE : l;
Ben Kwae4338342016-01-08 15:34:47 -0800366 }
367
Tomasz Mikolajewskid71bd612016-02-16 12:28:43 +0900368 public @Nullable Cursor getItem(String modelId) {
Ben Kwa0497da82015-11-30 23:00:02 -0800369 Integer pos = mPositions.get(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700370 if (pos == null) {
371 if (DEBUG) Log.d(TAG, "Unabled to find cursor position for modelId: " + modelId);
372 return null;
Ben Kwa0497da82015-11-30 23:00:02 -0800373 }
Steve McKay5a22a112016-04-12 11:29:10 -0700374
375 if (!mCursor.moveToPosition(pos)) {
376 if (DEBUG) Log.d(TAG,
377 "Unabled to move cursor to position " + pos + " for modelId: " + modelId);
378 return null;
379 }
380
381 return mCursor;
Ben Kwa0497da82015-11-30 23:00:02 -0800382 }
383
Ben Kwa0497da82015-11-30 23:00:02 -0800384 boolean isEmpty() {
385 return mCursorCount == 0;
386 }
387
388 boolean isLoading() {
389 return mIsLoading;
390 }
391
Steve McKay84769b82016-06-09 10:46:07 -0700392 List<DocumentInfo> getDocuments(Selection selection) {
393 final int size = (selection != null) ? selection.size() : 0;
Ben Kwa0497da82015-11-30 23:00:02 -0800394
395 final List<DocumentInfo> docs = new ArrayList<>(size);
Steve McKay84769b82016-06-09 10:46:07 -0700396 // NOTE: That as this now iterates over only final (non-provisional) selection.
397 for (String modelId: selection) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800398 final Cursor cursor = getItem(modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700399 if (cursor == null) {
Steve McKay84769b82016-06-09 10:46:07 -0700400 Log.w(TAG, "Skipping document. Unabled to obtain cursor for modelId: " + modelId);
Steve McKay5a22a112016-04-12 11:29:10 -0700401 continue;
402 }
Steve McKay0af8afd2016-02-25 13:34:03 -0800403 docs.add(DocumentInfo.fromDirectoryCursor(cursor));
Ben Kwa0497da82015-11-30 23:00:02 -0800404 }
405 return docs;
406 }
407
Steve McKay84769b82016-06-09 10:46:07 -0700408 public Uri getItemUri(String modelId) {
409 final Cursor cursor = getItem(modelId);
410 return DocumentInfo.getUri(cursor);
411 }
412
Ben Kwa0497da82015-11-30 23:00:02 -0800413 void addUpdateListener(UpdateListener listener) {
Ben Kwad72a1da2015-12-01 19:56:57 -0800414 mUpdateListeners.add(listener);
Ben Kwa0497da82015-11-30 23:00:02 -0800415 }
416
Ben Kwa472103f2016-02-10 15:48:25 -0800417 void removeUpdateListener(UpdateListener listener) {
418 mUpdateListeners.remove(listener);
419 }
420
Ben Kwad72a1da2015-12-01 19:56:57 -0800421 static interface UpdateListener {
Ben Kwa0497da82015-11-30 23:00:02 -0800422 /**
423 * Called when a successful update has occurred.
424 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800425 void onModelUpdate(Model model);
Ben Kwa0497da82015-11-30 23:00:02 -0800426
427 /**
428 * Called when an update has been attempted but failed.
429 */
Ben Kwad72a1da2015-12-01 19:56:57 -0800430 void onModelUpdateFailed(Exception e);
Ben Kwa0497da82015-11-30 23:00:02 -0800431 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800432
433 /**
434 * @return An ordered array of model IDs representing the documents in the model. It is sorted
435 * according to the current sort order, which was set by the last model update.
436 */
Tomasz Mikolajewskif27c2742016-03-07 18:01:45 +0900437 public String[] getModelIds() {
Ben Kwab8a5e082015-12-07 13:25:27 -0800438 return mIds;
439 }
Ben Kwa0497da82015-11-30 23:00:02 -0800440}