blob: a2b6ad51d3463e3596258af28e4bfcb7020c80f4 [file] [log] [blame]
Ben Kwada858bf2015-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 Kwada858bf2015-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;
24import android.net.Uri;
25import android.os.Bundle;
26import android.provider.DocumentsContract;
27import android.provider.DocumentsContract.Document;
28import android.support.v7.widget.RecyclerView;
29import android.test.AndroidTestCase;
30import android.test.mock.MockContentProvider;
31import android.test.mock.MockContentResolver;
32import android.test.suitebuilder.annotation.SmallTest;
33import android.view.ViewGroup;
34
35import com.android.documentsui.DirectoryResult;
36import com.android.documentsui.RootCursorWrapper;
Ben Kwab8a5e082015-12-07 13:25:27 -080037import com.android.documentsui.State;
Ben Kwada858bf2015-12-09 14:33:49 -080038import com.android.documentsui.dirlist.MultiSelectManager.Selection;
39import com.android.documentsui.model.DocumentInfo;
40
41import java.util.ArrayList;
Ben Kwab8a5e082015-12-07 13:25:27 -080042import java.util.BitSet;
Ben Kwada858bf2015-12-09 14:33:49 -080043import java.util.List;
Ben Kwab8a5e082015-12-07 13:25:27 -080044import java.util.Random;
Ben Kwada858bf2015-12-09 14:33:49 -080045import java.util.concurrent.CountDownLatch;
46
47@SmallTest
48public class ModelTest extends AndroidTestCase {
49
50 private static final int ITEM_COUNT = 10;
51 private static final String AUTHORITY = "test_authority";
52 private static final String[] COLUMNS = new String[]{
53 RootCursorWrapper.COLUMN_AUTHORITY,
54 Document.COLUMN_DOCUMENT_ID,
Ben Kwab8a5e082015-12-07 13:25:27 -080055 Document.COLUMN_FLAGS,
56 Document.COLUMN_DISPLAY_NAME,
57 Document.COLUMN_SIZE
Ben Kwada858bf2015-12-09 14:33:49 -080058 };
59 private static Cursor cursor;
60
61 private Context context;
62 private Model model;
63 private TestContentProvider provider;
Ben Kwab8a5e082015-12-07 13:25:27 -080064 private static final String[] NAMES = new String[] {
65 "4",
66 "foo",
67 "1",
68 "bar",
69 "*(Ljifl;a",
70 "0",
71 "baz",
72 "2",
73 "3",
74 "%$%VD"
75 };
Ben Kwada858bf2015-12-09 14:33:49 -080076
77 public void setUp() {
78 setupTestContext();
79
Ben Kwab8a5e082015-12-07 13:25:27 -080080 Random rand = new Random();
81
Ben Kwada858bf2015-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 Kwab8a5e082015-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 Kwada858bf2015-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.
99 model = new Model(context, new DummyAdapter());
100 model.addUpdateListener(new DummyListener());
101 model.update(r);
102 }
103
Ben Kwab8a5e082015-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 Kwada858bf2015-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 Kwab8a5e082015-12-07 13:25:27 -0800120 MatrixCursor cIn = new MatrixCursor(COLUMNS);
Ben Kwada858bf2015-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 Kwab8a5e082015-12-07 13:25:27 -0800126 MatrixCursor.RowBuilder row0 = cIn.newRow();
Ben Kwada858bf2015-12-09 14:33:49 -0800127 row0.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY0);
128 row0.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i));
129
Ben Kwab8a5e082015-12-07 13:25:27 -0800130 MatrixCursor.RowBuilder row1 = cIn.newRow();
Ben Kwada858bf2015-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 Kwab8a5e082015-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 Kwada858bf2015-12-09 14:33:49 -0800160 }
Ben Kwab8a5e082015-12-07 13:25:27 -0800161
162 assertEquals(ITEM_COUNT, b0.cardinality());
163 assertEquals(ITEM_COUNT, b1.cardinality());
Ben Kwada858bf2015-12-09 14:33:49 -0800164 }
165
166 // Tests the base case for Model.getItem.
167 public void testGetItem() {
Ben Kwab8a5e082015-12-07 13:25:27 -0800168 List<String> ids = model.getModelIds();
169 assertEquals(ITEM_COUNT, ids.size());
Ben Kwada858bf2015-12-09 14:33:49 -0800170 for (int i = 0; i < ITEM_COUNT; ++i) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800171 Cursor c = model.getItem(ids.get(i));
Ben Kwada858bf2015-12-09 14:33:49 -0800172 assertEquals(i, c.getPosition());
173 }
174 }
175
Ben Kwab8a5e082015-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() {
200 BitSet seen = new BitSet(ITEM_COUNT);
201 List<Integer> sizes = new ArrayList<>();
202
203 DirectoryResult r = new DirectoryResult();
204 r.cursor = cursor;
205 r.sortOrder = State.SORT_ORDER_SIZE;
206 model.update(r);
207
208 for (String id: model.getModelIds()) {
209 Cursor c = model.getItem(id);
210 seen.set(c.getPosition());
211 sizes.add(DocumentInfo.getCursorInt(c, Document.COLUMN_SIZE));
212 }
213
214 assertEquals(ITEM_COUNT, seen.cardinality());
215 for (int i = 0; i < sizes.size()-1; ++i) {
216 // Note: sizes are sorted descending.
217 assertTrue(sizes.get(i) >= sizes.get(i+1));
218 }
219 }
220
221
Ben Kwada858bf2015-12-09 14:33:49 -0800222 // Tests that Model.delete works correctly.
223 public void testDelete() throws Exception {
224 // Simulate deleting 2 files.
225 List<DocumentInfo> docsBefore = getDocumentInfo(2, 3);
226 delete(2, 3);
227
228 provider.assertWasDeleted(docsBefore.get(0));
229 provider.assertWasDeleted(docsBefore.get(1));
230 }
231
232 private void setupTestContext() {
233 final MockContentResolver resolver = new MockContentResolver();
234 context = new ContextWrapper(getContext()) {
235 @Override
236 public ContentResolver getContentResolver() {
237 return resolver;
238 }
239 };
240 provider = new TestContentProvider();
241 resolver.addProvider(AUTHORITY, provider);
242 }
243
244 private Selection positionToSelection(int... positions) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800245 List<String> ids = model.getModelIds();
Ben Kwada858bf2015-12-09 14:33:49 -0800246 Selection s = new Selection();
247 // Construct a selection of the given positions.
248 for (int p: positions) {
Ben Kwab8a5e082015-12-07 13:25:27 -0800249 s.add(ids.get(p));
Ben Kwada858bf2015-12-09 14:33:49 -0800250 }
251 return s;
252 }
253
254 private void delete(int... positions) throws InterruptedException {
255 Selection s = positionToSelection(positions);
256 final CountDownLatch latch = new CountDownLatch(1);
257
258 model.delete(
259 s,
260 new Model.DeletionListener() {
261 @Override
262 public void onError() {
263 latch.countDown();
264 }
265 @Override
266 void onCompletion() {
267 latch.countDown();
268 }
269 });
270 latch.await();
271 }
272
273 private List<DocumentInfo> getDocumentInfo(int... positions) {
274 return model.getDocuments(positionToSelection(positions));
275 }
276
277 private static class DummyListener implements Model.UpdateListener {
278 public void onModelUpdate(Model model) {}
279 public void onModelUpdateFailed(Exception e) {}
280 }
281
282 private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
283 public int getItemCount() { return 0; }
284 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
285 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
286 return null;
287 }
288 }
289
290 private static class TestContentProvider extends MockContentProvider {
291 List<Uri> mDeleted = new ArrayList<>();
292
293 @Override
294 public Bundle call(String method, String arg, Bundle extras) {
295 // Intercept and log delete method calls.
296 if (DocumentsContract.METHOD_DELETE_DOCUMENT.equals(method)) {
297 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
298 mDeleted.add(documentUri);
299 return new Bundle();
300 } else {
301 return super.call(method, arg, extras);
302 }
303 }
304
305 public void assertWasDeleted(DocumentInfo doc) {
306 assertTrue(mDeleted.contains(doc.derivedUri));
307 }
308 }
309}