blob: 89869d955baa9139a62772763e23a2b5619a234b [file] [log] [blame]
/*
* 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;
}
}
}