Move sorting back to loading thread.
Bug: 31062455
Change-Id: If248f7556d8d70b626546cecabf3377e531fd5b2
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 9f5e66c..d9d6cf4 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -47,7 +47,6 @@
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index ab79d01..d2c7d10 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -25,7 +25,6 @@
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.RemoteException;
-import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.util.Log;
@@ -35,8 +34,6 @@
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.sorting.SortModel;
-import java.io.FileNotFoundException;
-
import libcore.io.IoUtils;
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
@@ -86,7 +83,6 @@
final DirectoryResult result = new DirectoryResult();
result.doc = mDoc;
- result.sortModel = mModel;
ContentProviderClient client = null;
Cursor cursor;
@@ -107,6 +103,8 @@
cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
}
+ cursor = mModel.sortCursor(cursor);
+
result.client = client;
result.cursor = cursor;
} catch (Exception e) {
diff --git a/src/com/android/documentsui/DirectoryResult.java b/src/com/android/documentsui/DirectoryResult.java
index 32910ce..9e4d9f3 100644
--- a/src/com/android/documentsui/DirectoryResult.java
+++ b/src/com/android/documentsui/DirectoryResult.java
@@ -20,7 +20,6 @@
import android.database.Cursor;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.sorting.SortModel;
import libcore.io.IoUtils;
@@ -29,7 +28,6 @@
public Cursor cursor;
public Exception exception;
public DocumentInfo doc;
- public SortModel sortModel;
@Override
public void close() {
@@ -37,6 +35,6 @@
ContentProviderClient.releaseQuietly(client);
cursor = null;
client = null;
- sortModel = null;
+ doc = null;
}
}
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index dc4d9f2..6ce1896 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -40,10 +40,10 @@
import com.android.documentsui.roots.RootsAccess;
import com.android.internal.annotations.GuardedBy;
-import com.google.common.util.concurrent.AbstractFuture;
-
import libcore.io.IoUtils;
+import com.google.common.util.concurrent.AbstractFuture;
+
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -180,7 +180,6 @@
}
final DirectoryResult result = new DirectoryResult();
- result.sortModel = mState.sortModel;
final Cursor merged;
if (cursors.size() > 0) {
@@ -190,13 +189,16 @@
merged = new MatrixCursor(new String[0]);
}
+
+ final Cursor sorted = mState.sortModel.sortCursor(merged);
+
// Tell the UI if this is an in-progress result. When loading is complete, another update is
// sent with EXTRA_LOADING set to false.
Bundle extras = new Bundle();
extras.putBoolean(DocumentsContract.EXTRA_LOADING, !allDone);
- merged.setExtras(extras);
+ sorted.setExtras(extras);
- result.cursor = merged;
+ result.cursor = sorted;
return result;
}
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index 7973101..6c08859 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -16,7 +16,6 @@
package com.android.documentsui.dirlist;
-import static com.android.documentsui.base.DocumentInfo.getCursorLong;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import static com.android.documentsui.base.Shared.DEBUG;
@@ -34,11 +33,8 @@
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventListener;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.roots.RootCursorWrapper;
-import com.android.documentsui.sorting.SortDimension;
-import com.android.documentsui.sorting.SortModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,12 +56,7 @@
private int mCursorCount;
/** Maps Model ID to cursor positions, for looking up items by Model ID. */
private Map<String, Integer> mPositions = new HashMap<>();
- /**
- * A sorted array of model IDs for the files currently in the Model. Sort order is determined
- * by {@link #mSortModel}
- */
private String mIds[] = new String[0];
- private SortModel mSortModel;
@Nullable String info;
@Nullable String error;
@@ -126,7 +117,6 @@
mCursor = result.cursor;
mCursorCount = mCursor.getCount();
- mSortModel = result.sortModel;
doc = result.doc;
updateModelData();
@@ -151,24 +141,7 @@
* according to the current sort order.
*/
private void updateModelData() {
- int[] positions = new int[mCursorCount];
mIds = new String[mCursorCount];
- boolean[] isDirs = new boolean[mCursorCount];
- String[] displayNames = null;
- long[] longValues = null;
-
- final int id = mSortModel.getSortedDimensionId();
- switch (id) {
- case SortModel.SORT_DIMENSION_ID_TITLE:
- displayNames = new String[mCursorCount];
- break;
- case SortModel.SORT_DIMENSION_ID_DATE:
- case SortModel.SORT_DIMENSION_ID_SIZE:
- longValues = new long[mCursorCount];
- break;
- }
-
- String mimeType;
mCursor.moveToPosition(-1);
for (int pos = 0; pos < mCursorCount; ++pos) {
@@ -176,246 +149,25 @@
Log.e(TAG, "Fail to move cursor to next pos: " + pos);
return;
}
- positions[pos] = pos;
-
// Generates a Model ID for a cursor entry that refers to a document. The Model ID is a
// unique string that can be used to identify the document referred to by the cursor.
// If the cursor is a merged cursor over multiple authorities, then prefix the ids
// with the authority to avoid collisions.
if (mCursor instanceof MergeCursor) {
- mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY) + "|" +
- getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
+ mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
+ + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
} else {
mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
}
-
- mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
- isDirs[pos] = Document.MIME_TYPE_DIR.equals(mimeType);
-
- switch(id) {
- case SortModel.SORT_DIMENSION_ID_TITLE:
- final String displayName = getCursorString(
- mCursor, Document.COLUMN_DISPLAY_NAME);
- displayNames[pos] = displayName;
- break;
- case SortModel.SORT_DIMENSION_ID_DATE:
- longValues[pos] = getLastModified(mCursor);
- break;
- case SortModel.SORT_DIMENSION_ID_SIZE:
- longValues[pos] = getCursorLong(mCursor, Document.COLUMN_SIZE);
- break;
- }
- }
-
- final SortDimension dimension = mSortModel.getDimensionById(id);
- switch (id) {
- case SortModel.SORT_DIMENSION_ID_TITLE:
- binarySort(displayNames, isDirs, positions, mIds, dimension.getSortDirection());
- break;
- case SortModel.SORT_DIMENSION_ID_DATE:
- case SortModel.SORT_DIMENSION_ID_SIZE:
- binarySort(longValues, isDirs, positions, mIds, dimension.getSortDirection());
- break;
}
// Populate the positions.
mPositions.clear();
for (int i = 0; i < mCursorCount; ++i) {
- mPositions.put(mIds[i], positions[i]);
+ mPositions.put(mIds[i], i);
}
}
- /**
- * Sorts model data. Takes three columns of index-corresponded data. The first column is the
- * sort key. Rows are sorted in ascending alphabetical order on the sort key.
- * Directories are always shown first. This code is based on TimSort.binarySort().
- *
- * @param sortKey Data is sorted in ascending alphabetical order.
- * @param isDirs Array saying whether an item is a directory or not.
- * @param positions Cursor positions to be sorted.
- * @param ids Model IDs to be sorted.
- */
- private static void binarySort(
- String[] sortKey,
- boolean[] isDirs,
- int[] positions,
- String[] ids,
- @SortDimension.SortDirection int direction) {
- final int count = positions.length;
- for (int start = 1; start < count; start++) {
- final int pivotPosition = positions[start];
- final String pivotValue = sortKey[start];
- final boolean pivotIsDir = isDirs[start];
- final String pivotId = ids[start];
-
- int left = 0;
- int right = start;
-
- while (left < right) {
- int mid = (left + right) >>> 1;
-
- // Directories always go in front.
- int compare = 0;
- final boolean rhsIsDir = isDirs[mid];
- if (pivotIsDir && !rhsIsDir) {
- compare = -1;
- } else if (!pivotIsDir && rhsIsDir) {
- compare = 1;
- } else {
- final String lhs = pivotValue;
- final String rhs = sortKey[mid];
- switch (direction) {
- case SortDimension.SORT_DIRECTION_ASCENDING:
- compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
- break;
- case SortDimension.SORT_DIRECTION_DESCENDING:
- compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
- break;
- default:
- throw new IllegalArgumentException(
- "Unknown sorting direction: " + direction);
- }
- }
-
- if (compare < 0) {
- right = mid;
- } else {
- left = mid + 1;
- }
- }
-
- int n = start - left;
- switch (n) {
- case 2:
- positions[left + 2] = positions[left + 1];
- sortKey[left + 2] = sortKey[left + 1];
- isDirs[left + 2] = isDirs[left + 1];
- ids[left + 2] = ids[left + 1];
- case 1:
- positions[left + 1] = positions[left];
- sortKey[left + 1] = sortKey[left];
- isDirs[left + 1] = isDirs[left];
- ids[left + 1] = ids[left];
- break;
- default:
- System.arraycopy(positions, left, positions, left + 1, n);
- System.arraycopy(sortKey, left, sortKey, left + 1, n);
- System.arraycopy(isDirs, left, isDirs, left + 1, n);
- System.arraycopy(ids, left, ids, left + 1, n);
- }
-
- positions[left] = pivotPosition;
- sortKey[left] = pivotValue;
- isDirs[left] = pivotIsDir;
- ids[left] = pivotId;
- }
- }
-
- /**
- * Sorts model data. Takes four columns of index-corresponded data. The first column is the sort
- * key, and the second is an array of mime types. The rows are first bucketed by mime type
- * (directories vs documents) and then each bucket is sorted independently in descending
- * numerical order on the sort key. This code is based on TimSort.binarySort().
- *
- * @param sortKey Data is sorted in descending numerical order.
- * @param isDirs Array saying whether an item is a directory or not.
- * @param positions Cursor positions to be sorted.
- * @param ids Model IDs to be sorted.
- */
- private static void binarySort(
- long[] sortKey,
- boolean[] isDirs,
- int[] positions,
- String[] ids,
- @SortDimension.SortDirection int direction) {
- final int count = positions.length;
- for (int start = 1; start < count; start++) {
- final int pivotPosition = positions[start];
- final long pivotValue = sortKey[start];
- final boolean pivotIsDir = isDirs[start];
- final String pivotId = ids[start];
-
- int left = 0;
- int right = start;
-
- while (left < right) {
- int mid = ((left + right) >>> 1);
-
- // Directories always go in front.
- int compare = 0;
- final boolean rhsIsDir = isDirs[mid];
- if (pivotIsDir && !rhsIsDir) {
- compare = -1;
- } else if (!pivotIsDir && rhsIsDir) {
- compare = 1;
- } else {
- final long lhs = pivotValue;
- final long rhs = sortKey[mid];
- switch (direction) {
- case SortDimension.SORT_DIRECTION_ASCENDING:
- compare = Long.compare(lhs, rhs);
- break;
- case SortDimension.SORT_DIRECTION_DESCENDING:
- compare = -Long.compare(lhs, rhs);
- break;
- default:
- throw new IllegalArgumentException(
- "Unknown sorting direction: " + direction);
- }
- }
-
- // If numerical comparison yields a tie, use document ID as a tie breaker. This
- // will yield stable results even if incoming items are continually shuffling and
- // have identical numerical sort keys. One common example of this scenario is seen
- // when sorting a set of active downloads by mod time.
- if (compare == 0) {
- compare = pivotId.compareTo(ids[mid]);
- }
-
- if (compare < 0) {
- right = mid;
- } else {
- left = mid + 1;
- }
- }
-
- int n = start - left;
- switch (n) {
- case 2:
- positions[left + 2] = positions[left + 1];
- sortKey[left + 2] = sortKey[left + 1];
- isDirs[left + 2] = isDirs[left + 1];
- ids[left + 2] = ids[left + 1];
- case 1:
- positions[left + 1] = positions[left];
- sortKey[left + 1] = sortKey[left];
- isDirs[left + 1] = isDirs[left];
- ids[left + 1] = ids[left];
- break;
- default:
- System.arraycopy(positions, left, positions, left + 1, n);
- System.arraycopy(sortKey, left, sortKey, left + 1, n);
- System.arraycopy(isDirs, left, isDirs, left + 1, n);
- System.arraycopy(ids, left, ids, left + 1, n);
- }
-
- positions[left] = pivotPosition;
- sortKey[left] = pivotValue;
- isDirs[left] = pivotIsDir;
- ids[left] = pivotId;
- }
- }
-
- /**
- * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
- * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
- * when sorting descending by date.
- */
- long getLastModified(Cursor cursor) {
- long l = getCursorLong(mCursor, Document.COLUMN_LAST_MODIFIED);
- return (l == -1) ? Long.MAX_VALUE : l;
- }
-
public @Nullable Cursor getItem(String modelId) {
Integer pos = mPositions.get(modelId);
if (pos == null) {
diff --git a/src/com/android/documentsui/sorting/SortModel.java b/src/com/android/documentsui/sorting/SortModel.java
index 793dd60..f64bc28 100644
--- a/src/com/android/documentsui/sorting/SortModel.java
+++ b/src/com/android/documentsui/sorting/SortModel.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract.Document;
@@ -239,6 +240,14 @@
notifyListeners(UPDATE_TYPE_VISIBILITY);
}
+ public Cursor sortCursor(Cursor cursor) {
+ if (mSortedDimension != null) {
+ return new SortingCursorWrapper(cursor, mSortedDimension);
+ } else {
+ return cursor;
+ }
+ }
+
public @Nullable String getDocumentSortQuery() {
final int id = getSortedDimensionId();
final String columnName;
diff --git a/src/com/android/documentsui/sorting/SortingCursorWrapper.java b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
new file mode 100644
index 0000000..89869d9
--- /dev/null
+++ b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
@@ -0,0 +1,330 @@
+/*
+ * 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.getCursorLong;
+import static com.android.documentsui.base.DocumentInfo.getCursorString;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.sorting.SortModel.SortDimensionId;
+
+/**
+ * Cursor wrapper that presents a sorted view of the underlying cursor. Handles
+ * common {@link Document} sorting modes, such as ordering directories first.
+ */
+class SortingCursorWrapper extends AbstractCursor {
+ private final Cursor mCursor;
+
+ private final int[] mPosition;
+
+ public SortingCursorWrapper(Cursor cursor, SortDimension dimension) {
+ mCursor = cursor;
+
+ final int count = cursor.getCount();
+ mPosition = new int[count];
+ boolean[] isDirs = new boolean[count];
+ String[] displayNames = null;
+ long[] longValues = null;
+ String[] ids = null;
+
+ final @SortDimensionId int id = dimension.getId();
+ switch (id) {
+ case SortModel.SORT_DIMENSION_ID_TITLE:
+ displayNames = new String[count];
+ break;
+ case SortModel.SORT_DIMENSION_ID_DATE:
+ case SortModel.SORT_DIMENSION_ID_SIZE:
+ longValues = new long[count];
+ ids = new String[count];
+ break;
+ }
+
+ cursor.moveToPosition(-1);
+ for (int i = 0; i < count; i++) {
+ cursor.moveToNext();
+ mPosition[i] = i;
+
+ final String mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
+ isDirs[i] = Document.MIME_TYPE_DIR.equals(mimeType);
+
+ switch(id) {
+ case SortModel.SORT_DIMENSION_ID_TITLE:
+ final String displayName = getCursorString(
+ mCursor, Document.COLUMN_DISPLAY_NAME);
+ displayNames[i] = displayName;
+ break;
+ case SortModel.SORT_DIMENSION_ID_DATE:
+ longValues[i] = getLastModified(mCursor);
+ ids[i] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
+ break;
+ case SortModel.SORT_DIMENSION_ID_SIZE:
+ longValues[i] = getCursorLong(mCursor, Document.COLUMN_SIZE);
+ ids[i] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
+ break;
+ }
+
+ }
+
+ switch (id) {
+ case SortModel.SORT_DIMENSION_ID_TITLE:
+ binarySort(displayNames, isDirs, mPosition, dimension.getSortDirection());
+ break;
+ case SortModel.SORT_DIMENSION_ID_DATE:
+ case SortModel.SORT_DIMENSION_ID_SIZE:
+ binarySort(longValues, isDirs, mPosition, ids, dimension.getSortDirection());
+ break;
+ }
+
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ mCursor.close();
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mCursor.moveToPosition(mPosition[newPosition]);
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return mCursor.getColumnNames();
+ }
+
+ @Override
+ public int getCount() {
+ return mCursor.getCount();
+ }
+
+ @Override
+ public double getDouble(int column) {
+ return mCursor.getDouble(column);
+ }
+
+ @Override
+ public float getFloat(int column) {
+ return mCursor.getFloat(column);
+ }
+
+ @Override
+ public int getInt(int column) {
+ return mCursor.getInt(column);
+ }
+
+ @Override
+ public long getLong(int column) {
+ return mCursor.getLong(column);
+ }
+
+ @Override
+ public short getShort(int column) {
+ return mCursor.getShort(column);
+ }
+
+ @Override
+ public String getString(int column) {
+ return mCursor.getString(column);
+ }
+
+ @Override
+ public int getType(int column) {
+ return mCursor.getType(column);
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return mCursor.isNull(column);
+ }
+
+ /**
+ * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
+ * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
+ * when sorting descending by date.
+ */
+ private static long getLastModified(Cursor cursor) {
+ long l = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+ return (l == -1) ? Long.MAX_VALUE : l;
+ }
+
+ /**
+ * Borrowed from TimSort.binarySort(), but modified to sort two column
+ * dataset.
+ */
+ private static void binarySort(
+ String[] sortKey,
+ boolean[] isDirs,
+ int[] positions,
+ @SortDimension.SortDirection int direction) {
+ final int count = positions.length;
+ for (int start = 1; start < count; start++) {
+ final int pivotPosition = positions[start];
+ final String pivotValue = sortKey[start];
+ final boolean pivotIsDir = isDirs[start];
+
+ int left = 0;
+ int right = start;
+
+ while (left < right) {
+ int mid = (left + right) >>> 1;
+
+ // Directories always go in front.
+ int compare = 0;
+ final boolean rhsIsDir = isDirs[mid];
+ if (pivotIsDir && !rhsIsDir) {
+ compare = -1;
+ } else if (!pivotIsDir && rhsIsDir) {
+ compare = 1;
+ } else {
+ final String lhs = pivotValue;
+ final String rhs = sortKey[mid];
+ switch (direction) {
+ case SortDimension.SORT_DIRECTION_ASCENDING:
+ compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
+ break;
+ case SortDimension.SORT_DIRECTION_DESCENDING:
+ compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown sorting direction: " + direction);
+ }
+ }
+
+ if (compare < 0) {
+ right = mid;
+ } else {
+ left = mid + 1;
+ }
+ }
+
+ int n = start - left;
+ switch (n) {
+ case 2:
+ positions[left + 2] = positions[left + 1];
+ sortKey[left + 2] = sortKey[left + 1];
+ isDirs[left + 2] = isDirs[left + 1];
+ case 1:
+ positions[left + 1] = positions[left];
+ sortKey[left + 1] = sortKey[left];
+ isDirs[left + 1] = isDirs[left];
+ break;
+ default:
+ System.arraycopy(positions, left, positions, left + 1, n);
+ System.arraycopy(sortKey, left, sortKey, left + 1, n);
+ System.arraycopy(isDirs, left, isDirs, left + 1, n);
+ }
+
+ positions[left] = pivotPosition;
+ sortKey[left] = pivotValue;
+ isDirs[left] = pivotIsDir;
+ }
+ }
+
+ /**
+ * Borrowed from TimSort.binarySort(), but modified to sort two column
+ * dataset.
+ */
+ private static void binarySort(
+ long[] sortKey,
+ boolean[] isDirs,
+ int[] positions,
+ String[] ids,
+ @SortDimension.SortDirection int direction) {
+ final int count = positions.length;
+ for (int start = 1; start < count; start++) {
+ final int pivotPosition = positions[start];
+ final long pivotValue = sortKey[start];
+ final boolean pivotIsDir = isDirs[start];
+ final String pivotId = ids[start];
+
+ int left = 0;
+ int right = start;
+
+ while (left < right) {
+ int mid = ((left + right) >>> 1);
+
+ // Directories always go in front.
+ int compare = 0;
+ final boolean rhsIsDir = isDirs[mid];
+ if (pivotIsDir && !rhsIsDir) {
+ compare = -1;
+ } else if (!pivotIsDir && rhsIsDir) {
+ compare = 1;
+ } else {
+ final long lhs = pivotValue;
+ final long rhs = sortKey[mid];
+ switch (direction) {
+ case SortDimension.SORT_DIRECTION_ASCENDING:
+ compare = Long.compare(lhs, rhs);
+ break;
+ case SortDimension.SORT_DIRECTION_DESCENDING:
+ compare = -Long.compare(lhs, rhs);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown sorting direction: " + direction);
+ }
+ }
+
+ // If numerical comparison yields a tie, use document ID as a tie breaker. This
+ // will yield stable results even if incoming items are continually shuffling and
+ // have identical numerical sort keys. One common example of this scenario is seen
+ // when sorting a set of active downloads by mod time.
+ if (compare == 0) {
+ compare = pivotId.compareTo(ids[mid]);
+ }
+
+ if (compare < 0) {
+ right = mid;
+ } else {
+ left = mid + 1;
+ }
+ }
+
+ int n = start - left;
+ switch (n) {
+ case 2:
+ positions[left + 2] = positions[left + 1];
+ sortKey[left + 2] = sortKey[left + 1];
+ isDirs[left + 2] = isDirs[left + 1];
+ ids[left + 2] = ids[left + 1];
+ case 1:
+ positions[left + 1] = positions[left];
+ sortKey[left + 1] = sortKey[left];
+ isDirs[left + 1] = isDirs[left];
+ ids[left + 1] = ids[left];
+ break;
+ default:
+ System.arraycopy(positions, left, positions, left + 1, n);
+ System.arraycopy(sortKey, left, sortKey, left + 1, n);
+ System.arraycopy(isDirs, left, isDirs, left + 1, n);
+ System.arraycopy(ids, left, ids, left + 1, n);
+ }
+
+ positions[left] = pivotPosition;
+ sortKey[left] = pivotValue;
+ isDirs[left] = pivotIsDir;
+ ids[left] = pivotId;
+ }
+ }
+}
diff --git a/tests/common/com/android/documentsui/dirlist/TestModel.java b/tests/common/com/android/documentsui/dirlist/TestModel.java
index 0c3ca92..1204400 100644
--- a/tests/common/com/android/documentsui/dirlist/TestModel.java
+++ b/tests/common/com/android/documentsui/dirlist/TestModel.java
@@ -21,7 +21,6 @@
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.roots.RootCursorWrapper;
-import com.android.documentsui.testing.SortModels;
import java.util.Random;
@@ -60,7 +59,6 @@
DirectoryResult r = new DirectoryResult();
r.cursor = c;
- r.sortModel = SortModels.createTestSortModel();
update(r);
}
diff --git a/tests/unit/com/android/documentsui/dirlist/ModelTest.java b/tests/unit/com/android/documentsui/dirlist/ModelTest.java
index b9dab0d..faad880 100644
--- a/tests/unit/com/android/documentsui/dirlist/ModelTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/ModelTest.java
@@ -74,7 +74,6 @@
private Cursor cursor;
private Model model;
private TestContentProvider provider;
- private SortModel sortModel;
@Override
public void setUp() {
@@ -94,11 +93,9 @@
row.add(Document.COLUMN_SIZE, rand.nextInt());
}
cursor = c;
- sortModel = SortModels.createTestSortModel();
DirectoryResult r = new DirectoryResult();
r.cursor = cursor;
- r.sortModel = sortModel;
// Instantiate the model with a dummy view adapter and listener that (for now) do nothing.
model = new Model();
@@ -136,7 +133,6 @@
// Update the model, then make sure it contains all the expected items.
DirectoryResult r = new DirectoryResult();
r.cursor = cIn;
- r.sortModel = SortModels.createTestSortModel();
model.update(r);
assertEquals(ITEM_COUNT * 2, model.getItemCount());
@@ -174,291 +170,6 @@
assertEquals(i, c.getPosition());
}
}
-
- // Tests sorting ascending by item name.
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = cursor;
- r.sortModel = sortModel;
- model.update(r);
-
- for (String id: model.getModelIds()) {
- Cursor c = model.getItem(id);
- seen.set(c.getPosition());
- names.add(DocumentInfo.getCursorString(c, 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.
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = cursor;
- r.sortModel = sortModel;
- model.update(r);
-
- for (String id: model.getModelIds()) {
- Cursor c = model.getItem(id);
- seen.set(c.getPosition());
- names.add(DocumentInfo.getCursorString(c, 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.
- public void testSort_sizes_ascending() {
- sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE,
- SortDimension.SORT_DIRECTION_ASCENDING);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = cursor;
- r.sortModel = sortModel;
- model.update(r);
-
- BitSet seen = new BitSet(ITEM_COUNT);
- int previousSize = Integer.MIN_VALUE;
- for (String id: model.getModelIds()) {
- Cursor c = model.getItem(id);
- seen.set(c.getPosition());
- // 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 sorting by item size.
- public void testSort_sizes_descending() {
- sortModel.sortByUser(SortModel.SORT_DIMENSION_ID_SIZE,
- SortDimension.SORT_DIRECTION_DESCENDING);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = cursor;
- r.sortModel = sortModel;
- model.update(r);
-
- BitSet seen = new BitSet(ITEM_COUNT);
- int previousSize = Integer.MAX_VALUE;
- for (String id: model.getModelIds()) {
- Cursor c = model.getItem(id);
- seen.set(c.getPosition());
- // 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
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = c;
- r.sortModel = sortModel;
- model.update(r);
-
- 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 (String id: model.getModelIds()) {
- Cursor cOut = model.getItem(id);
- seen.set(cOut.getPosition());
-
- String mimeType = DocumentInfo.getCursorString(cOut, 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
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = c;
- r.sortModel = sortModel;
- model.update(r);
-
- 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 (String id: model.getModelIds()) {
- Cursor cOut = model.getItem(id);
- seen.set(cOut.getPosition());
-
- String mimeType = DocumentInfo.getCursorString(cOut, 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());
- }
-
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = c;
- r.sortModel = sortModel;
- model.update(r);
-
- String[] ids = model.getModelIds();
-
- // Check that all items were accounted for
- assertEquals(ITEM_COUNT + DL_COUNT, ids.length);
-
- // Check that active downloads are sorted to the bottom.
- for (int i = ITEM_COUNT; i < ITEM_COUNT + DL_COUNT; i++) {
- assertTrue(currentDownloads.contains(ids[i]));
- }
- }
-
- 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);
-
- DirectoryResult r = new DirectoryResult();
- r.cursor = c;
- r.sortModel = sortModel;
- model.update(r);
-
- String[] ids = model.getModelIds();
-
- // Check that all items were accounted for
- assertEquals(ITEM_COUNT + DL_COUNT, ids.length);
-
- // Check that active downloads are sorted to the top.
- for (int i = 0; i < DL_COUNT; i++) {
- assertTrue(currentDownloads.contains(ids[i]));
- }
- }
-
private void setupTestContext() {
final MockContentResolver resolver = new MockContentResolver();
new ContextWrapper(getContext()) {
diff --git a/tests/unit/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java b/tests/unit/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
index dec287b..d44a467 100644
--- a/tests/unit/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
@@ -77,7 +77,6 @@
}
DirectoryResult r = new DirectoryResult();
r.cursor = c;
- r.sortModel = SortModels.createTestSortModel();
mModel.update(r);
assertEquals(mModel.getItemCount(), mAdapter.getItemCount());
@@ -103,7 +102,6 @@
}
DirectoryResult r = new DirectoryResult();
r.cursor = c;
- r.sortModel = SortModels.createTestSortModel();
mModel.update(r);
assertEquals(mModel.getItemCount() + 1, mAdapter.getItemCount());
diff --git a/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
new file mode 100644
index 0000000..1d6a09c
--- /dev/null
+++ b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.provider.DocumentsContract.Document;
+import android.support.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 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)
+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 SortModel sortModel;
+ private Cursor cursor;
+
+ @Before
+ public void setUp() {
+ sortModel = SortModels.createTestSortModel();
+
+ 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());
+ }
+
+ 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)));
+ }
+ }
+
+ private Cursor createSortingCursorWrapper() {
+ return createSortingCursorWrapper(cursor);
+ }
+
+ private Cursor createSortingCursorWrapper(Cursor c) {
+ final @SortDimensionId int id = sortModel.getSortedDimensionId();
+ return new SortingCursorWrapper(c, sortModel.getDimensionById(id));
+ }
+}