1/*
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 android.support.v7.widget.RecyclerView;
20import android.test.AndroidTestCase;
21import android.test.suitebuilder.annotation.SmallTest;
22import android.util.SparseBooleanArray;
23
24import com.android.documentsui.TestInputEvent;
25import com.android.documentsui.dirlist.MultiSelectManager.Selection;
26import com.google.common.collect.Lists;
27
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.List;
31import java.util.Set;
32
33@SmallTest
34public class MultiSelectManagerTest extends AndroidTestCase {
35
36    private static final List<String> items;
37    static {
38        items = new ArrayList<String>();
39        for (int i = 0; i < 100; ++i) {
40            items.add(Integer.toString(i));
41        }
42    }
43
44    private MultiSelectManager mManager;
45    private TestCallback mCallback;
46    private TestSelectionEnvironment mEnv;
47    private TestDocumentsAdapter mAdapter;
48
49    public void setUp() throws Exception {
50        mCallback = new TestCallback();
51        mEnv = new TestSelectionEnvironment(items);
52        mAdapter = new TestDocumentsAdapter(items);
53        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE, null);
54        mManager.addCallback(mCallback);
55    }
56
57    public void testSelection() {
58        // Check selection.
59        mManager.toggleSelection(items.get(7));
60        assertSelection(items.get(7));
61        // Check deselection.
62        mManager.toggleSelection(items.get(7));
63        assertSelectionSize(0);
64    }
65
66    public void testSelection_NotifiesSelectionChanged() {
67        // Selection should notify.
68        mManager.toggleSelection(items.get(7));
69        mCallback.assertSelectionChanged();
70        // Deselection should notify.
71        mManager.toggleSelection(items.get(7));
72        mCallback.assertSelectionChanged();
73    }
74
75    public void testMouseClick_ShiftClickExtendsSelection() {
76        longPress(7);
77        shiftClick(11);
78        assertRangeSelection(7, 11);
79    }
80
81    public void testMouseClick_NoPosition_ClearsSelection() {
82        longPress(7);
83        click(11);
84        click(RecyclerView.NO_POSITION);
85        assertSelection();
86    }
87
88    public void testSetSelectionFocusBegin() {
89        mManager.setItemsSelected(Lists.newArrayList(items.get(7)), true);
90        mManager.setSelectionRangeBegin(7);
91        shiftClick(11);
92        assertRangeSelection(7, 11);
93    }
94
95    public void testLongPress_StartsSelectionMode() {
96        longPress(7);
97        assertSelection(items.get(7));
98    }
99
100    public void testLongPress_SecondPressExtendsSelection() {
101        longPress(7);
102        longPress(99);
103        assertSelection(items.get(7), items.get(99));
104    }
105
106    public void testSingleTapUp_UnselectsSelectedItem() {
107        longPress(7);
108        tap(7);
109        assertSelection();
110    }
111
112    public void testSingleTapUp_NoPosition_ClearsSelection() {
113        longPress(7);
114        tap(11);
115        tap(RecyclerView.NO_POSITION);
116        assertSelection();
117    }
118
119    public void testSingleTapUp_ExtendsSelection() {
120        longPress(99);
121        tap(7);
122        tap(13);
123        assertSelection(items.get(7), items.get(99), items.get(13));
124    }
125
126    public void testSingleTapUp_ShiftCreatesRangeSelection() {
127        longPress(7);
128        shiftTap(17);
129        assertRangeSelection(7, 17);
130    }
131
132    public void testSingleTapUp_ShiftCreatesRangeSeletion_Backwards() {
133        longPress(17);
134        shiftTap(7);
135        assertRangeSelection(7, 17);
136    }
137
138    public void testSingleTapUp_SecondShiftClickExtendsSelection() {
139        longPress(7);
140        shiftTap(11);
141        shiftTap(17);
142        assertRangeSelection(7, 17);
143    }
144
145    public void testSingleTapUp_MultipleContiguousRangesSelected() {
146        longPress(7);
147        shiftTap(11);
148        tap(20);
149        shiftTap(25);
150        assertRangeSelected(7, 11);
151        assertRangeSelected(20, 25);
152        assertSelectionSize(11);
153    }
154
155    public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick() {
156        longPress(7);
157        shiftTap(17);
158        shiftTap(10);
159        assertRangeSelection(7, 10);
160    }
161
162    public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick_Backwards() {
163        mManager.onLongPress(TestInputEvent.tap(17));
164        shiftTap(7);
165        shiftTap(14);
166        assertRangeSelection(14, 17);
167    }
168
169    public void testSingleTapUp_ShiftReversesSelectionDirection() {
170        longPress(7);
171        shiftTap(17);
172        shiftTap(0);
173        assertRangeSelection(0, 7);
174    }
175
176    public void testSingleSelectMode() {
177        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
178        mManager.addCallback(mCallback);
179        longPress(20);
180        tap(13);
181        assertSelection(items.get(13));
182    }
183
184    public void testSingleSelectMode_ShiftTap() {
185        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
186        mManager.addCallback(mCallback);
187        longPress(13);
188        shiftTap(20);
189        assertSelection(items.get(20));
190    }
191
192    public void testRangeSelection() {
193        mManager.startRangeSelection(15);
194        mManager.snapRangeSelection(19);
195        assertRangeSelection(15, 19);
196    }
197
198    public void testRangeSelection_snapExpand() {
199        mManager.startRangeSelection(15);
200        mManager.snapRangeSelection(19);
201        mManager.snapRangeSelection(27);
202        assertRangeSelection(15, 27);
203    }
204
205    public void testRangeSelection_snapContract() {
206        mManager.startRangeSelection(15);
207        mManager.snapRangeSelection(27);
208        mManager.snapRangeSelection(19);
209        assertRangeSelection(15, 19);
210    }
211
212    public void testRangeSelection_snapInvert() {
213        mManager.startRangeSelection(15);
214        mManager.snapRangeSelection(27);
215        mManager.snapRangeSelection(3);
216        assertRangeSelection(3, 15);
217    }
218
219    public void testRangeSelection_multiple() {
220        mManager.startRangeSelection(15);
221        mManager.snapRangeSelection(27);
222        mManager.endRangeSelection();
223        mManager.startRangeSelection(42);
224        mManager.snapRangeSelection(57);
225        assertSelectionSize(29);
226        assertRangeSelected(15, 27);
227        assertRangeSelected(42, 57);
228
229    }
230
231    public void testRangeSelection_singleSelect() {
232        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
233        mManager.addCallback(mCallback);
234        mManager.startRangeSelection(11);
235        mManager.snapRangeSelection(19);
236        assertSelectionSize(1);
237        assertSelection(items.get(19));
238    }
239
240    public void testProvisionalSelection() {
241        Selection s = mManager.getSelection();
242        assertSelection();
243
244        SparseBooleanArray provisional = new SparseBooleanArray();
245        provisional.append(1, true);
246        provisional.append(2, true);
247        s.setProvisionalSelection(getItemIds(provisional));
248        assertSelection(items.get(1), items.get(2));
249    }
250
251    public void testProvisionalSelection_Replace() {
252        Selection s = mManager.getSelection();
253
254        SparseBooleanArray provisional = new SparseBooleanArray();
255        provisional.append(1, true);
256        provisional.append(2, true);
257        s.setProvisionalSelection(getItemIds(provisional));
258
259        provisional.clear();
260        provisional.append(3, true);
261        provisional.append(4, true);
262        s.setProvisionalSelection(getItemIds(provisional));
263        assertSelection(items.get(3), items.get(4));
264    }
265
266    public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
267        Selection s = mManager.getSelection();
268
269        SparseBooleanArray provisional = new SparseBooleanArray();
270        provisional.append(1, true);
271        provisional.append(2, true);
272        s.setProvisionalSelection(getItemIds(provisional));
273
274        provisional.clear();
275        provisional.append(1, true);
276        s.setProvisionalSelection(getItemIds(provisional));
277        assertSelection(items.get(1));
278    }
279
280    public void testProvisionalSelection_Apply() {
281        Selection s = mManager.getSelection();
282
283        SparseBooleanArray provisional = new SparseBooleanArray();
284        provisional.append(1, true);
285        provisional.append(2, true);
286        s.setProvisionalSelection(getItemIds(provisional));
287        s.applyProvisionalSelection();
288        assertSelection(items.get(1), items.get(2));
289    }
290
291    public void testProvisionalSelection_Cancel() {
292        mManager.toggleSelection(items.get(1));
293        mManager.toggleSelection(items.get(2));
294        Selection s = mManager.getSelection();
295
296        SparseBooleanArray provisional = new SparseBooleanArray();
297        provisional.append(3, true);
298        provisional.append(4, true);
299        s.setProvisionalSelection(getItemIds(provisional));
300        s.cancelProvisionalSelection();
301
302        // Original selection should remain.
303        assertSelection(items.get(1), items.get(2));
304    }
305
306    public void testProvisionalSelection_IntersectsAppliedSelection() {
307        mManager.toggleSelection(items.get(1));
308        mManager.toggleSelection(items.get(2));
309        Selection s = mManager.getSelection();
310
311        SparseBooleanArray provisional = new SparseBooleanArray();
312        provisional.append(2, true);
313        provisional.append(3, true);
314        s.setProvisionalSelection(getItemIds(provisional));
315        assertSelection(items.get(1), items.get(2), items.get(3));
316    }
317
318    private static Set<String> getItemIds(SparseBooleanArray selection) {
319        Set<String> ids = new HashSet<>();
320
321        int count = selection.size();
322        for (int i = 0; i < count; ++i) {
323            ids.add(items.get(selection.keyAt(i)));
324        }
325
326        return ids;
327    }
328
329    private void longPress(int position) {
330        mManager.onLongPress(TestInputEvent.tap(position));
331    }
332
333    private void tap(int position) {
334        mManager.onSingleTapUp(TestInputEvent.tap(position));
335    }
336
337    private void shiftTap(int position) {
338        mManager.onSingleTapUp(TestInputEvent.shiftTap(position));
339    }
340
341    private void click(int position) {
342        mManager.onSingleTapUp(TestInputEvent.click(position));
343    }
344
345    private void shiftClick(int position) {
346        mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
347    }
348
349    private void assertSelected(String... expected) {
350        for (int i = 0; i < expected.length; i++) {
351            Selection selection = mManager.getSelection();
352            String err = String.format(
353                    "Selection %s does not contain %s", selection, expected[i]);
354            assertTrue(err, selection.contains(expected[i]));
355        }
356    }
357
358    private void assertSelection(String... expected) {
359        assertSelectionSize(expected.length);
360        assertSelected(expected);
361    }
362
363    private void assertRangeSelected(int begin, int end) {
364        for (int i = begin; i <= end; i++) {
365            assertSelected(items.get(i));
366        }
367    }
368
369    private void assertRangeSelection(int begin, int end) {
370        assertSelectionSize(end - begin + 1);
371        assertRangeSelected(begin, end);
372    }
373
374    private void assertSelectionSize(int expected) {
375        Selection selection = mManager.getSelection();
376        assertEquals(selection.toString(), expected, selection.size());
377    }
378
379    private static final class TestCallback implements MultiSelectManager.Callback {
380
381        Set<String> ignored = new HashSet<>();
382        private boolean mSelectionChanged = false;
383
384        @Override
385        public void onItemStateChanged(String modelId, boolean selected) {}
386
387        @Override
388        public boolean onBeforeItemStateChange(String modelId, boolean selected) {
389            return !ignored.contains(modelId);
390        }
391
392        @Override
393        public void onSelectionChanged() {
394            mSelectionChanged = true;
395        }
396
397        void assertSelectionChanged() {
398            assertTrue(mSelectionChanged);
399        }
400    }
401}
402