blob: 691c1d750ddfe646e53041dd608e9d5490040e39 [file] [log] [blame]
Garfield Tan2010ff72016-09-30 14:55:32 -07001/*
2 * Copyright (C) 2016 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.sorting;
18
19import static com.android.documentsui.base.DocumentInfo.getCursorLong;
20import static com.android.documentsui.base.DocumentInfo.getCursorString;
21
22import android.database.AbstractCursor;
23import android.database.Cursor;
Steve McKayb324dfd2016-10-28 12:52:36 -070024import android.os.Bundle;
Garfield Tan2010ff72016-09-30 14:55:32 -070025import android.provider.DocumentsContract.Document;
26
27import com.android.documentsui.base.Shared;
28import com.android.documentsui.sorting.SortModel.SortDimensionId;
29
30/**
31 * Cursor wrapper that presents a sorted view of the underlying cursor. Handles
32 * common {@link Document} sorting modes, such as ordering directories first.
33 */
34class SortingCursorWrapper extends AbstractCursor {
35 private final Cursor mCursor;
36
37 private final int[] mPosition;
38
39 public SortingCursorWrapper(Cursor cursor, SortDimension dimension) {
40 mCursor = cursor;
41
42 final int count = cursor.getCount();
43 mPosition = new int[count];
44 boolean[] isDirs = new boolean[count];
45 String[] displayNames = null;
46 long[] longValues = null;
47 String[] ids = null;
48
49 final @SortDimensionId int id = dimension.getId();
50 switch (id) {
51 case SortModel.SORT_DIMENSION_ID_TITLE:
52 displayNames = new String[count];
53 break;
54 case SortModel.SORT_DIMENSION_ID_DATE:
55 case SortModel.SORT_DIMENSION_ID_SIZE:
56 longValues = new long[count];
57 ids = new String[count];
58 break;
59 }
60
61 cursor.moveToPosition(-1);
62 for (int i = 0; i < count; i++) {
63 cursor.moveToNext();
64 mPosition[i] = i;
65
66 final String mimeType = getCursorString(mCursor, Document.COLUMN_MIME_TYPE);
67 isDirs[i] = Document.MIME_TYPE_DIR.equals(mimeType);
68
69 switch(id) {
70 case SortModel.SORT_DIMENSION_ID_TITLE:
71 final String displayName = getCursorString(
72 mCursor, Document.COLUMN_DISPLAY_NAME);
73 displayNames[i] = displayName;
74 break;
75 case SortModel.SORT_DIMENSION_ID_DATE:
76 longValues[i] = getLastModified(mCursor);
77 ids[i] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
78 break;
79 case SortModel.SORT_DIMENSION_ID_SIZE:
80 longValues[i] = getCursorLong(mCursor, Document.COLUMN_SIZE);
81 ids[i] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
82 break;
83 }
84
85 }
86
87 switch (id) {
88 case SortModel.SORT_DIMENSION_ID_TITLE:
89 binarySort(displayNames, isDirs, mPosition, dimension.getSortDirection());
90 break;
91 case SortModel.SORT_DIMENSION_ID_DATE:
92 case SortModel.SORT_DIMENSION_ID_SIZE:
93 binarySort(longValues, isDirs, mPosition, ids, dimension.getSortDirection());
94 break;
95 }
96
97 }
98
99 @Override
100 public void close() {
101 super.close();
102 mCursor.close();
103 }
104
105 @Override
106 public boolean onMove(int oldPosition, int newPosition) {
107 return mCursor.moveToPosition(mPosition[newPosition]);
108 }
109
110 @Override
111 public String[] getColumnNames() {
112 return mCursor.getColumnNames();
113 }
114
115 @Override
116 public int getCount() {
117 return mCursor.getCount();
118 }
119
120 @Override
121 public double getDouble(int column) {
122 return mCursor.getDouble(column);
123 }
124
125 @Override
126 public float getFloat(int column) {
127 return mCursor.getFloat(column);
128 }
129
130 @Override
131 public int getInt(int column) {
132 return mCursor.getInt(column);
133 }
134
135 @Override
136 public long getLong(int column) {
137 return mCursor.getLong(column);
138 }
139
140 @Override
141 public short getShort(int column) {
142 return mCursor.getShort(column);
143 }
144
145 @Override
146 public String getString(int column) {
147 return mCursor.getString(column);
148 }
149
150 @Override
151 public int getType(int column) {
152 return mCursor.getType(column);
153 }
154
155 @Override
156 public boolean isNull(int column) {
157 return mCursor.isNull(column);
158 }
159
Steve McKayb324dfd2016-10-28 12:52:36 -0700160 @Override
161 public Bundle getExtras() {
162 return mCursor.getExtras();
163 }
164
Garfield Tan2010ff72016-09-30 14:55:32 -0700165 /**
166 * @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
167 * timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
168 * when sorting descending by date.
169 */
170 private static long getLastModified(Cursor cursor) {
171 long l = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
172 return (l == -1) ? Long.MAX_VALUE : l;
173 }
174
175 /**
176 * Borrowed from TimSort.binarySort(), but modified to sort two column
177 * dataset.
178 */
179 private static void binarySort(
180 String[] sortKey,
181 boolean[] isDirs,
182 int[] positions,
183 @SortDimension.SortDirection int direction) {
184 final int count = positions.length;
185 for (int start = 1; start < count; start++) {
186 final int pivotPosition = positions[start];
187 final String pivotValue = sortKey[start];
188 final boolean pivotIsDir = isDirs[start];
189
190 int left = 0;
191 int right = start;
192
193 while (left < right) {
194 int mid = (left + right) >>> 1;
195
196 // Directories always go in front.
197 int compare = 0;
198 final boolean rhsIsDir = isDirs[mid];
199 if (pivotIsDir && !rhsIsDir) {
200 compare = -1;
201 } else if (!pivotIsDir && rhsIsDir) {
202 compare = 1;
203 } else {
204 final String lhs = pivotValue;
205 final String rhs = sortKey[mid];
206 switch (direction) {
207 case SortDimension.SORT_DIRECTION_ASCENDING:
208 compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
209 break;
210 case SortDimension.SORT_DIRECTION_DESCENDING:
211 compare = -Shared.compareToIgnoreCaseNullable(lhs, rhs);
212 break;
213 default:
214 throw new IllegalArgumentException(
215 "Unknown sorting direction: " + direction);
216 }
217 }
218
219 if (compare < 0) {
220 right = mid;
221 } else {
222 left = mid + 1;
223 }
224 }
225
226 int n = start - left;
227 switch (n) {
228 case 2:
229 positions[left + 2] = positions[left + 1];
230 sortKey[left + 2] = sortKey[left + 1];
231 isDirs[left + 2] = isDirs[left + 1];
232 case 1:
233 positions[left + 1] = positions[left];
234 sortKey[left + 1] = sortKey[left];
235 isDirs[left + 1] = isDirs[left];
236 break;
237 default:
238 System.arraycopy(positions, left, positions, left + 1, n);
239 System.arraycopy(sortKey, left, sortKey, left + 1, n);
240 System.arraycopy(isDirs, left, isDirs, left + 1, n);
241 }
242
243 positions[left] = pivotPosition;
244 sortKey[left] = pivotValue;
245 isDirs[left] = pivotIsDir;
246 }
247 }
248
249 /**
250 * Borrowed from TimSort.binarySort(), but modified to sort two column
251 * dataset.
252 */
253 private static void binarySort(
254 long[] sortKey,
255 boolean[] isDirs,
256 int[] positions,
257 String[] ids,
258 @SortDimension.SortDirection int direction) {
259 final int count = positions.length;
260 for (int start = 1; start < count; start++) {
261 final int pivotPosition = positions[start];
262 final long pivotValue = sortKey[start];
263 final boolean pivotIsDir = isDirs[start];
264 final String pivotId = ids[start];
265
266 int left = 0;
267 int right = start;
268
269 while (left < right) {
270 int mid = ((left + right) >>> 1);
271
272 // Directories always go in front.
273 int compare = 0;
274 final boolean rhsIsDir = isDirs[mid];
275 if (pivotIsDir && !rhsIsDir) {
276 compare = -1;
277 } else if (!pivotIsDir && rhsIsDir) {
278 compare = 1;
279 } else {
280 final long lhs = pivotValue;
281 final long rhs = sortKey[mid];
282 switch (direction) {
283 case SortDimension.SORT_DIRECTION_ASCENDING:
284 compare = Long.compare(lhs, rhs);
285 break;
286 case SortDimension.SORT_DIRECTION_DESCENDING:
287 compare = -Long.compare(lhs, rhs);
288 break;
289 default:
290 throw new IllegalArgumentException(
291 "Unknown sorting direction: " + direction);
292 }
293 }
294
295 // If numerical comparison yields a tie, use document ID as a tie breaker. This
296 // will yield stable results even if incoming items are continually shuffling and
297 // have identical numerical sort keys. One common example of this scenario is seen
298 // when sorting a set of active downloads by mod time.
299 if (compare == 0) {
300 compare = pivotId.compareTo(ids[mid]);
301 }
302
303 if (compare < 0) {
304 right = mid;
305 } else {
306 left = mid + 1;
307 }
308 }
309
310 int n = start - left;
311 switch (n) {
312 case 2:
313 positions[left + 2] = positions[left + 1];
314 sortKey[left + 2] = sortKey[left + 1];
315 isDirs[left + 2] = isDirs[left + 1];
316 ids[left + 2] = ids[left + 1];
317 case 1:
318 positions[left + 1] = positions[left];
319 sortKey[left + 1] = sortKey[left];
320 isDirs[left + 1] = isDirs[left];
321 ids[left + 1] = ids[left];
322 break;
323 default:
324 System.arraycopy(positions, left, positions, left + 1, n);
325 System.arraycopy(sortKey, left, sortKey, left + 1, n);
326 System.arraycopy(isDirs, left, isDirs, left + 1, n);
327 System.arraycopy(ids, left, ids, left + 1, n);
328 }
329
330 positions[left] = pivotPosition;
331 sortKey[left] = pivotValue;
332 isDirs[left] = pivotIsDir;
333 ids[left] = pivotId;
334 }
335 }
336}