| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.documentsui.sorting; |
| |
| import static com.android.documentsui.base.DocumentInfo.getCursorString; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertFalse; |
| import static junit.framework.Assert.assertTrue; |
| |
| import android.database.Cursor; |
| import android.database.MatrixCursor; |
| import android.os.Bundle; |
| import android.provider.DocumentsContract; |
| import android.provider.DocumentsContract.Document; |
| |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.documentsui.base.DocumentInfo; |
| import com.android.documentsui.base.Shared; |
| import com.android.documentsui.roots.RootCursorWrapper; |
| import com.android.documentsui.sorting.SortModel.SortDimensionId; |
| import com.android.documentsui.testing.SortModels; |
| import com.android.documentsui.testing.TestFileTypeLookup; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Random; |
| import java.util.Set; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class SortingCursorWrapperTest { |
| private static final int ITEM_COUNT = 10; |
| private static final String AUTHORITY = "test_authority"; |
| |
| private static final String[] COLUMNS = new String[]{ |
| RootCursorWrapper.COLUMN_AUTHORITY, |
| Document.COLUMN_DOCUMENT_ID, |
| Document.COLUMN_FLAGS, |
| Document.COLUMN_DISPLAY_NAME, |
| Document.COLUMN_SIZE, |
| Document.COLUMN_LAST_MODIFIED, |
| Document.COLUMN_MIME_TYPE |
| }; |
| |
| private static final String[] NAMES = new String[] { |
| "4", |
| "foo", |
| "1", |
| "bar", |
| "*(Ljifl;a", |
| "0", |
| "baz", |
| "2", |
| "3", |
| "%$%VD" |
| }; |
| |
| private static final String[] MIMES = new String[] { |
| "application/zip", |
| "video/3gp", |
| "image/png", |
| "text/plain", |
| "application/msword", |
| "text/html", |
| "application/pdf", |
| "image/png", |
| "audio/flac", |
| "audio/mp3" |
| }; |
| |
| private static final String[] TYPES = new String[] { |
| "Zip archive", |
| "3GP video", |
| "PNG image", |
| "Plain text", |
| "Word document", |
| "HTML document", |
| "PDF document", |
| "PNG image", |
| "FLAC audio", |
| "MP3 audio" |
| }; |
| |
| private TestFileTypeLookup fileTypeLookup; |
| private SortModel sortModel; |
| private Cursor cursor; |
| |
| @Before |
| public void setUp() { |
| sortModel = SortModels.createTestSortModel(); |
| fileTypeLookup = new TestFileTypeLookup(); |
| |
| Random rand = new Random(); |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); |
| row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE); |
| // Generate random document names and sizes. This forces the model's internal sort code |
| // to actually do something. |
| row.add(Document.COLUMN_DISPLAY_NAME, NAMES[i]); |
| row.add(Document.COLUMN_SIZE, rand.nextInt()); |
| row.add(Document.COLUMN_MIME_TYPE, MIMES[i]); |
| } |
| |
| cursor = c; |
| } |
| |
| // Tests sorting ascending by item name. |
| @Test |
| public void testSort_names_ascending() { |
| BitSet seen = new BitSet(ITEM_COUNT); |
| List<String> names = new ArrayList<>(); |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_TITLE, |
| SortDimension.SORT_DIRECTION_ASCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(); |
| |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(Integer.parseInt( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| names.add(getCursorString(cursor, Document.COLUMN_DISPLAY_NAME)); |
| } |
| |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| for (int i = 0; i < names.size()-1; ++i) { |
| assertTrue(Shared.compareToIgnoreCaseNullable(names.get(i), names.get(i+1)) <= 0); |
| } |
| } |
| |
| // Tests sorting descending by item name. |
| @Test |
| public void testSort_names_descending() { |
| BitSet seen = new BitSet(ITEM_COUNT); |
| List<String> names = new ArrayList<>(); |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_TITLE, |
| SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(); |
| |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(Integer.parseInt( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| names.add(getCursorString(cursor, Document.COLUMN_DISPLAY_NAME)); |
| } |
| |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| for (int i = 0; i < names.size()-1; ++i) { |
| assertTrue(Shared.compareToIgnoreCaseNullable(names.get(i), names.get(i+1)) >= 0); |
| } |
| } |
| |
| // Tests sorting by item size. |
| @Test |
| public void testSort_sizes_ascending() { |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE, |
| SortDimension.SORT_DIRECTION_ASCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(); |
| |
| BitSet seen = new BitSet(ITEM_COUNT); |
| int previousSize = Integer.MIN_VALUE; |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(Integer.parseInt( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| // Check sort order - descending numerical |
| int size = DocumentInfo.getCursorInt(cursor, Document.COLUMN_SIZE); |
| assertTrue(previousSize <= size); |
| previousSize = size; |
| } |
| // Check that all items were accounted for. |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| } |
| |
| // Tests sorting by item size. |
| @Test |
| public void testSort_sizes_descending() { |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE, |
| SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| Cursor cursor = createSortingCursorWrapper(); |
| |
| BitSet seen = new BitSet(ITEM_COUNT); |
| int previousSize = Integer.MAX_VALUE; |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(Integer.parseInt( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| // Check sort order - descending numerical |
| int size = DocumentInfo.getCursorInt(cursor, Document.COLUMN_SIZE); |
| assertTrue(previousSize >= size); |
| previousSize = size; |
| } |
| // Check that all items were accounted for. |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| } |
| |
| // Tests that directories and files are properly bucketed when sorting by size |
| @Test |
| public void testSort_sizesWithBucketing_ascending() { |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); |
| row.add(Document.COLUMN_SIZE, i); |
| // Interleave directories and text files. |
| String mimeType =(i % 2 == 0) ? Document.MIME_TYPE_DIR : "text/*"; |
| row.add(Document.COLUMN_MIME_TYPE, mimeType); |
| } |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE, |
| SortDimension.SORT_DIRECTION_ASCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(c); |
| |
| boolean seenAllDirs = false; |
| int previousSize = Integer.MIN_VALUE; |
| BitSet seen = new BitSet(ITEM_COUNT); |
| // Iterate over items in sort order. Once we've encountered a document (i.e. not a |
| // directory), all subsequent items must also be documents. That is, all directories are |
| // bucketed at the front of the list, sorted by size, followed by documents, sorted by size. |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(Integer.parseInt( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| |
| String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); |
| if (seenAllDirs) { |
| assertFalse(Document.MIME_TYPE_DIR.equals(mimeType)); |
| } else { |
| if (!Document.MIME_TYPE_DIR.equals(mimeType)) { |
| seenAllDirs = true; |
| // Reset the previous size seen, because documents are bucketed separately by |
| // the sort. |
| previousSize = Integer.MIN_VALUE; |
| } |
| } |
| // Check sort order - descending numerical |
| int size = DocumentInfo.getCursorInt(c, Document.COLUMN_SIZE); |
| assertTrue(previousSize <= size); |
| previousSize = size; |
| } |
| |
| // Check that all items were accounted for. |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| } |
| |
| // Tests that directories and files are properly bucketed when sorting by size |
| @Test |
| public void testSort_sizesWithBucketing_descending() { |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); |
| row.add(Document.COLUMN_SIZE, i); |
| // Interleave directories and text files. |
| String mimeType =(i % 2 == 0) ? Document.MIME_TYPE_DIR : "text/*"; |
| row.add(Document.COLUMN_MIME_TYPE, mimeType); |
| } |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE, |
| SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(c); |
| |
| boolean seenAllDirs = false; |
| int previousSize = Integer.MAX_VALUE; |
| BitSet seen = new BitSet(ITEM_COUNT); |
| // Iterate over items in sort order. Once we've encountered a document (i.e. not a |
| // directory), all subsequent items must also be documents. That is, all directories are |
| // bucketed at the front of the list, sorted by size, followed by documents, sorted by size. |
| for (int i = 0; i < cursor.getCount(); ++i) { |
| cursor.moveToPosition(i); |
| seen.set(cursor.getPosition()); |
| |
| String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); |
| if (seenAllDirs) { |
| assertFalse(Document.MIME_TYPE_DIR.equals(mimeType)); |
| } else { |
| if (!Document.MIME_TYPE_DIR.equals(mimeType)) { |
| seenAllDirs = true; |
| // Reset the previous size seen, because documents are bucketed separately by |
| // the sort. |
| previousSize = Integer.MAX_VALUE; |
| } |
| } |
| // Check sort order - descending numerical |
| int size = DocumentInfo.getCursorInt(c, Document.COLUMN_SIZE); |
| assertTrue(previousSize >= size); |
| previousSize = size; |
| } |
| |
| // Check that all items were accounted for. |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| } |
| |
| @Test |
| public void testSort_time_ascending() { |
| final int DL_COUNT = 3; |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| Set<String> currentDownloads = new HashSet<>(); |
| |
| // Add some files |
| for (int i = 0; i < ITEM_COUNT; i++) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); |
| row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); |
| } |
| // Add some current downloads (no timestamp) |
| for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| String id = Integer.toString(i); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, id); |
| currentDownloads.add(id); |
| } |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_DATE, |
| SortDimension.SORT_DIRECTION_ASCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(c); |
| |
| // Check that all items were accounted for |
| assertEquals(ITEM_COUNT + DL_COUNT, cursor.getCount()); |
| |
| // Check that active downloads are sorted to the bottom. |
| for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) { |
| assertTrue(currentDownloads.contains( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| } |
| } |
| |
| @Test |
| public void testSort_time_descending() { |
| final int DL_COUNT = 3; |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| Set<String> currentDownloads = new HashSet<>(); |
| |
| // Add some files |
| for (int i = 0; i < ITEM_COUNT; i++) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, Integer.toString(i)); |
| row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); |
| } |
| // Add some current downloads (no timestamp) |
| for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) { |
| MatrixCursor.RowBuilder row = c.newRow(); |
| String id = Integer.toString(i); |
| row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); |
| row.add(Document.COLUMN_DOCUMENT_ID, id); |
| currentDownloads.add(id); |
| } |
| |
| sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_DATE, |
| SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(c); |
| |
| // Check that all items were accounted for |
| assertEquals(ITEM_COUNT + DL_COUNT, cursor.getCount()); |
| |
| // Check that active downloads are sorted to the top. |
| for (int i = 0; i < DL_COUNT; i++) { |
| assertTrue(currentDownloads.contains( |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_DOCUMENT_ID))); |
| } |
| } |
| |
| @Test |
| public void testSort_type_ascending() { |
| populateTypeMap(); |
| |
| sortModel.sortByUser( |
| SortModel.SORT_DIMENSION_ID_FILE_TYPE, SortDimension.SORT_DIRECTION_ASCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(); |
| |
| assertEquals(ITEM_COUNT, cursor.getCount()); |
| final BitSet seen = new BitSet(ITEM_COUNT); |
| List<String> types = new ArrayList<>(ITEM_COUNT); |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| cursor.moveToPosition(i); |
| final String mime = |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_MIME_TYPE); |
| final String type = fileTypeLookup.lookup(mime); |
| types.add(type); |
| |
| seen.set(DocumentInfo.getCursorInt(cursor, Document.COLUMN_DOCUMENT_ID)); |
| } |
| |
| // Check all items were accounted for |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| for (int i = 0; i < ITEM_COUNT - 1; ++i) { |
| final String lhs = types.get(i); |
| final String rhs = types.get(i + 1); |
| assertTrue(lhs + " is not smaller than " + rhs, |
| Shared.compareToIgnoreCaseNullable(lhs, rhs) <= 0); |
| } |
| } |
| |
| @Test |
| public void testSort_type_descending() { |
| populateTypeMap(); |
| |
| sortModel.sortByUser( |
| SortModel.SORT_DIMENSION_ID_FILE_TYPE, SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| final Cursor cursor = createSortingCursorWrapper(); |
| |
| assertEquals(ITEM_COUNT, cursor.getCount()); |
| final BitSet seen = new BitSet(ITEM_COUNT); |
| List<String> types = new ArrayList<>(ITEM_COUNT); |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| cursor.moveToPosition(i); |
| final String mime = |
| DocumentInfo.getCursorString(cursor, Document.COLUMN_MIME_TYPE); |
| final String type = fileTypeLookup.lookup(mime); |
| types.add(type); |
| |
| seen.set(DocumentInfo.getCursorInt(cursor, Document.COLUMN_DOCUMENT_ID)); |
| } |
| |
| // Check all items were accounted for |
| assertEquals(ITEM_COUNT, seen.cardinality()); |
| for (int i = 0; i < ITEM_COUNT - 1; ++i) { |
| final String lhs = types.get(i); |
| final String rhs = types.get(i + 1); |
| assertTrue(lhs + " is not smaller than " + rhs, |
| Shared.compareToIgnoreCaseNullable(lhs, rhs) >= 0); |
| } |
| } |
| |
| private void populateTypeMap() { |
| for (int i = 0; i < ITEM_COUNT; ++i) { |
| fileTypeLookup.fileTypes.put(MIMES[i], TYPES[i]); |
| } |
| } |
| |
| @Test |
| public void testReturnsWrappedExtras() { |
| MatrixCursor c = new MatrixCursor(COLUMNS); |
| Bundle extras = new Bundle(); |
| extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); |
| extras.putString(DocumentsContract.EXTRA_INFO, "cheddar"); |
| extras.putString(DocumentsContract.EXTRA_ERROR, "flop"); |
| c.setExtras(extras); |
| |
| // set sorting to avoid an NPE. |
| sortModel.sortByUser( |
| SortModel.SORT_DIMENSION_ID_DATE, |
| SortDimension.SORT_DIRECTION_DESCENDING); |
| |
| Bundle actual = createSortingCursorWrapper(c).getExtras(); |
| |
| assertTrue(actual.getBoolean(DocumentsContract.EXTRA_LOADING, false)); |
| assertEquals("cheddar", actual.getString(DocumentsContract.EXTRA_INFO)); |
| assertEquals("flop", actual.getString(DocumentsContract.EXTRA_ERROR)); |
| } |
| |
| private Cursor createSortingCursorWrapper() { |
| return createSortingCursorWrapper(cursor); |
| } |
| |
| private Cursor createSortingCursorWrapper(Cursor c) { |
| final @SortDimensionId int id = sortModel.getSortedDimensionId(); |
| return new SortingCursorWrapper(c, sortModel.getDimensionById(id), fileTypeLookup); |
| } |
| } |