blob: a5f065638c96d0b6f2239551bcfc6e94b8a6b067 [file] [log] [blame]
Ben Kwadb65cd52015-12-09 14:33:49 -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
Ben Kwadb65cd52015-12-09 14:33:49 -080019import android.content.ContentResolver;
20import android.content.Context;
21import android.content.ContextWrapper;
22import android.database.Cursor;
23import android.database.MatrixCursor;
Ben Kwadb65cd52015-12-09 14:33:49 -080024import android.provider.DocumentsContract.Document;
Ben Kwadb65cd52015-12-09 14:33:49 -080025import android.test.AndroidTestCase;
Ben Kwadb65cd52015-12-09 14:33:49 -080026import android.test.mock.MockContentResolver;
27import android.test.suitebuilder.annotation.SmallTest;
Ben Kwadb65cd52015-12-09 14:33:49 -080028
29import com.android.documentsui.DirectoryResult;
30import com.android.documentsui.RootCursorWrapper;
Ben Kwa862b96412015-12-07 13:25:27 -080031import com.android.documentsui.State;
Ben Kwadb65cd52015-12-09 14:33:49 -080032import com.android.documentsui.dirlist.MultiSelectManager.Selection;
33import com.android.documentsui.model.DocumentInfo;
34
35import java.util.ArrayList;
Ben Kwa862b96412015-12-07 13:25:27 -080036import java.util.BitSet;
Ben Kwae2564f92016-01-08 15:34:47 -080037import java.util.HashSet;
Ben Kwadb65cd52015-12-09 14:33:49 -080038import java.util.List;
Ben Kwa862b96412015-12-07 13:25:27 -080039import java.util.Random;
Ben Kwae2564f92016-01-08 15:34:47 -080040import java.util.Set;
Ben Kwadb65cd52015-12-09 14:33:49 -080041import java.util.concurrent.CountDownLatch;
42
43@SmallTest
44public class ModelTest extends AndroidTestCase {
45
46 private static final int ITEM_COUNT = 10;
47 private static final String AUTHORITY = "test_authority";
Steve McKayef16f5f2015-12-22 18:15:31 -080048
Ben Kwadb65cd52015-12-09 14:33:49 -080049 private static final String[] COLUMNS = new String[]{
50 RootCursorWrapper.COLUMN_AUTHORITY,
51 Document.COLUMN_DOCUMENT_ID,
Ben Kwa862b96412015-12-07 13:25:27 -080052 Document.COLUMN_FLAGS,
53 Document.COLUMN_DISPLAY_NAME,
Ben Kwac72a2cb2015-12-16 19:42:08 -080054 Document.COLUMN_SIZE,
Ben Kwae2564f92016-01-08 15:34:47 -080055 Document.COLUMN_LAST_MODIFIED,
Ben Kwac72a2cb2015-12-16 19:42:08 -080056 Document.COLUMN_MIME_TYPE
Ben Kwadb65cd52015-12-09 14:33:49 -080057 };
Ben Kwadb65cd52015-12-09 14:33:49 -080058
Steve McKayef16f5f2015-12-22 18:15:31 -080059 private static final String[] NAMES = new String[] {
60 "4",
61 "foo",
62 "1",
63 "bar",
64 "*(Ljifl;a",
65 "0",
66 "baz",
67 "2",
68 "3",
69 "%$%VD"
70 };
71
72 private Cursor cursor;
Ben Kwadb65cd52015-12-09 14:33:49 -080073 private Context context;
74 private Model model;
75 private TestContentProvider provider;
76
77 public void setUp() {
78 setupTestContext();
79
Ben Kwa862b96412015-12-07 13:25:27 -080080 Random rand = new Random();
81
Ben Kwadb65cd52015-12-09 14:33:49 -080082 MatrixCursor c = new MatrixCursor(COLUMNS);
83 for (int i = 0; i < ITEM_COUNT; ++i) {
84 MatrixCursor.RowBuilder row = c.newRow();
85 row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
86 row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
87 row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE);
Ben Kwa862b96412015-12-07 13:25:27 -080088 // Generate random document names and sizes. This forces the model's internal sort code
89 // to actually do something.
90 row.add(Document.COLUMN_DISPLAY_NAME, NAMES[i]);
91 row.add(Document.COLUMN_SIZE, rand.nextInt());
Ben Kwadb65cd52015-12-09 14:33:49 -080092 }
93 cursor = c;
94
95 DirectoryResult r = new DirectoryResult();
96 r.cursor = cursor;
97
98 // Instantiate the model with a dummy view adapter and listener that (for now) do nothing.
Steve McKayef16f5f2015-12-22 18:15:31 -080099 model = new Model(context);
Ben Kwadb65cd52015-12-09 14:33:49 -0800100 model.addUpdateListener(new DummyListener());
101 model.update(r);
102 }
103
Ben Kwa862b96412015-12-07 13:25:27 -0800104 // Tests that the model is properly emptied out after a null update.
105 public void testNullUpdate() {
106 model.update(null);
107
108 assertTrue(model.isEmpty());
109 assertEquals(0, model.getItemCount());
110 assertEquals(0, model.getModelIds().size());
111 }
112
Ben Kwadb65cd52015-12-09 14:33:49 -0800113 // Tests that the item count is correct.
114 public void testItemCount() {
115 assertEquals(ITEM_COUNT, model.getItemCount());
116 }
117
118 // Tests multiple authorities with clashing document IDs.
119 public void testModelIdIsUnique() {
Ben Kwa862b96412015-12-07 13:25:27 -0800120 MatrixCursor cIn = new MatrixCursor(COLUMNS);
Ben Kwadb65cd52015-12-09 14:33:49 -0800121
122 // Make two sets of items with the same IDs, under different authorities.
123 final String AUTHORITY0 = "auth0";
124 final String AUTHORITY1 = "auth1";
125 for (int i = 0; i < ITEM_COUNT; ++i) {
Ben Kwa862b96412015-12-07 13:25:27 -0800126 MatrixCursor.RowBuilder row0 = cIn.newRow();
Ben Kwadb65cd52015-12-09 14:33:49 -0800127 row0.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY0);
128 row0.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
129
Ben Kwa862b96412015-12-07 13:25:27 -0800130 MatrixCursor.RowBuilder row1 = cIn.newRow();
Ben Kwadb65cd52015-12-09 14:33:49 -0800131 row1.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY1);
132 row1.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
133 }
134
Ben Kwa862b96412015-12-07 13:25:27 -0800135 // Update the model, then make sure it contains all the expected items.
136 DirectoryResult r = new DirectoryResult();
137 r.cursor = cIn;
138 model.update(r);
139
140 assertEquals(ITEM_COUNT * 2, model.getItemCount());
141 BitSet b0 = new BitSet(ITEM_COUNT);
142 BitSet b1 = new BitSet(ITEM_COUNT);
143
144 for (String id: model.getModelIds()) {
145 Cursor cOut = model.getItem(id);
146 String authority =
147 DocumentInfo.getCursorString(cOut, RootCursorWrapper.COLUMN_AUTHORITY);
148 String docId = DocumentInfo.getCursorString(cOut, Document.COLUMN_DOCUMENT_ID);
149
150 switch (authority) {
151 case AUTHORITY0:
152 b0.set(Integer.parseInt(docId));
153 break;
154 case AUTHORITY1:
155 b1.set(Integer.parseInt(docId));
156 break;
157 default:
158 fail("Unrecognized authority string");
159 }
Ben Kwadb65cd52015-12-09 14:33:49 -0800160 }
Ben Kwa862b96412015-12-07 13:25:27 -0800161
162 assertEquals(ITEM_COUNT, b0.cardinality());
163 assertEquals(ITEM_COUNT, b1.cardinality());
Ben Kwadb65cd52015-12-09 14:33:49 -0800164 }
165
166 // Tests the base case for Model.getItem.
167 public void testGetItem() {
Ben Kwa862b96412015-12-07 13:25:27 -0800168 List<String> ids = model.getModelIds();
169 assertEquals(ITEM_COUNT, ids.size());
Ben Kwadb65cd52015-12-09 14:33:49 -0800170 for (int i = 0; i < ITEM_COUNT; ++i) {
Ben Kwa862b96412015-12-07 13:25:27 -0800171 Cursor c = model.getItem(ids.get(i));
Ben Kwadb65cd52015-12-09 14:33:49 -0800172 assertEquals(i, c.getPosition());
173 }
174 }
175
Ben Kwa862b96412015-12-07 13:25:27 -0800176 // Tests sorting by item name.
177 public void testSort_names() {
178 BitSet seen = new BitSet(ITEM_COUNT);
179 List<String> names = new ArrayList<>();
180
181 DirectoryResult r = new DirectoryResult();
182 r.cursor = cursor;
183 r.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
184 model.update(r);
185
186 for (String id: model.getModelIds()) {
187 Cursor c = model.getItem(id);
188 seen.set(c.getPosition());
189 names.add(DocumentInfo.getCursorString(c, Document.COLUMN_DISPLAY_NAME));
190 }
191
192 assertEquals(ITEM_COUNT, seen.cardinality());
193 for (int i = 0; i < names.size()-1; ++i) {
194 assertTrue(DocumentInfo.compareToIgnoreCaseNullable(names.get(i), names.get(i+1)) <= 0);
195 }
196 }
197
198 // Tests sorting by item size.
199 public void testSort_sizes() {
Ben Kwa862b96412015-12-07 13:25:27 -0800200 DirectoryResult r = new DirectoryResult();
201 r.cursor = cursor;
202 r.sortOrder = State.SORT_ORDER_SIZE;
203 model.update(r);
204
Ben Kwac72a2cb2015-12-16 19:42:08 -0800205 BitSet seen = new BitSet(ITEM_COUNT);
206 int previousSize = Integer.MAX_VALUE;
Ben Kwa862b96412015-12-07 13:25:27 -0800207 for (String id: model.getModelIds()) {
208 Cursor c = model.getItem(id);
209 seen.set(c.getPosition());
Ben Kwac72a2cb2015-12-16 19:42:08 -0800210 // Check sort order - descending numerical
211 int size = DocumentInfo.getCursorInt(c, Document.COLUMN_SIZE);
212 assertTrue(previousSize >= size);
213 previousSize = size;
Ben Kwa862b96412015-12-07 13:25:27 -0800214 }
Ben Kwac72a2cb2015-12-16 19:42:08 -0800215 // Check that all items were accounted for.
Ben Kwa862b96412015-12-07 13:25:27 -0800216 assertEquals(ITEM_COUNT, seen.cardinality());
Ben Kwa862b96412015-12-07 13:25:27 -0800217 }
218
Ben Kwac72a2cb2015-12-16 19:42:08 -0800219 // Tests that directories and files are properly bucketed when sorting by size
220 public void testSort_sizesWithBucketing() {
221 MatrixCursor c = new MatrixCursor(COLUMNS);
222
223 for (int i = 0; i < ITEM_COUNT; ++i) {
224 MatrixCursor.RowBuilder row = c.newRow();
225 row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
226 row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
227 row.add(Document.COLUMN_SIZE, i);
228 // Interleave directories and text files.
229 String mimeType =(i % 2 == 0) ? Document.MIME_TYPE_DIR : "text/*";
230 row.add(Document.COLUMN_MIME_TYPE, mimeType);
231 }
232
233 DirectoryResult r = new DirectoryResult();
234 r.cursor = c;
235 r.sortOrder = State.SORT_ORDER_SIZE;
236 model.update(r);
237
238 boolean seenAllDirs = false;
239 int previousSize = Integer.MAX_VALUE;
240 BitSet seen = new BitSet(ITEM_COUNT);
241 // Iterate over items in sort order. Once we've encountered a document (i.e. not a
242 // directory), all subsequent items must also be documents. That is, all directories are
243 // bucketed at the front of the list, sorted by size, followed by documents, sorted by size.
244 for (String id: model.getModelIds()) {
245 Cursor cOut = model.getItem(id);
246 seen.set(cOut.getPosition());
247
248 String mimeType = DocumentInfo.getCursorString(cOut, Document.COLUMN_MIME_TYPE);
249 if (seenAllDirs) {
250 assertFalse(Document.MIME_TYPE_DIR.equals(mimeType));
251 } else {
252 if (!Document.MIME_TYPE_DIR.equals(mimeType)) {
253 seenAllDirs = true;
254 // Reset the previous size seen, because documents are bucketed separately by
255 // the sort.
256 previousSize = Integer.MAX_VALUE;
257 }
258 }
259 // Check sort order - descending numerical
260 int size = DocumentInfo.getCursorInt(c, Document.COLUMN_SIZE);
261 assertTrue(previousSize >= size);
262 previousSize = size;
263 }
264
265 // Check that all items were accounted for.
266 assertEquals(ITEM_COUNT, seen.cardinality());
267 }
Ben Kwa862b96412015-12-07 13:25:27 -0800268
Ben Kwae2564f92016-01-08 15:34:47 -0800269 public void testSort_time() {
270 final int DL_COUNT = 3;
271 MatrixCursor c = new MatrixCursor(COLUMNS);
272 Set<String> currentDownloads = new HashSet<>();
273
274 // Add some files
275 for (int i = 0; i < ITEM_COUNT; i++) {
276 MatrixCursor.RowBuilder row = c.newRow();
277 row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
278 row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
279 row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
280 }
281 // Add some current downloads (no timestamp)
282 for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) {
283 MatrixCursor.RowBuilder row = c.newRow();
284 String id = Integer.toString(i);
285 row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY);
286 row.add(Document.COLUMN_DOCUMENT_ID, id);
287 currentDownloads.add(Model.createModelId(AUTHORITY, id));
288 }
289
290 DirectoryResult r = new DirectoryResult();
291 r.cursor = c;
292 r.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
293 model.update(r);
294
295 List<String> ids = model.getModelIds();
296
297 // Check that all items were accounted for
298 assertEquals(ITEM_COUNT + DL_COUNT, ids.size());
299
300 // Check that active downloads are sorted to the top.
301 for (int i = 0; i < DL_COUNT; i++) {
302 assertTrue(currentDownloads.contains(ids.get(i)));
303 }
304 }
305
Ben Kwadb65cd52015-12-09 14:33:49 -0800306 // Tests that Model.delete works correctly.
307 public void testDelete() throws Exception {
308 // Simulate deleting 2 files.
309 List<DocumentInfo> docsBefore = getDocumentInfo(2, 3);
310 delete(2, 3);
311
312 provider.assertWasDeleted(docsBefore.get(0));
313 provider.assertWasDeleted(docsBefore.get(1));
314 }
315
316 private void setupTestContext() {
317 final MockContentResolver resolver = new MockContentResolver();
318 context = new ContextWrapper(getContext()) {
319 @Override
320 public ContentResolver getContentResolver() {
321 return resolver;
322 }
323 };
324 provider = new TestContentProvider();
325 resolver.addProvider(AUTHORITY, provider);
326 }
327
328 private Selection positionToSelection(int... positions) {
Ben Kwa862b96412015-12-07 13:25:27 -0800329 List<String> ids = model.getModelIds();
Ben Kwadb65cd52015-12-09 14:33:49 -0800330 Selection s = new Selection();
331 // Construct a selection of the given positions.
332 for (int p: positions) {
Ben Kwa862b96412015-12-07 13:25:27 -0800333 s.add(ids.get(p));
Ben Kwadb65cd52015-12-09 14:33:49 -0800334 }
335 return s;
336 }
337
338 private void delete(int... positions) throws InterruptedException {
339 Selection s = positionToSelection(positions);
340 final CountDownLatch latch = new CountDownLatch(1);
341
342 model.delete(
343 s,
344 new Model.DeletionListener() {
345 @Override
346 public void onError() {
347 latch.countDown();
348 }
349 @Override
350 void onCompletion() {
351 latch.countDown();
352 }
353 });
354 latch.await();
355 }
356
357 private List<DocumentInfo> getDocumentInfo(int... positions) {
358 return model.getDocuments(positionToSelection(positions));
359 }
360
361 private static class DummyListener implements Model.UpdateListener {
362 public void onModelUpdate(Model model) {}
363 public void onModelUpdateFailed(Exception e) {}
364 }
Ben Kwadb65cd52015-12-09 14:33:49 -0800365}