1/*
2 * Copyright (C) 2013 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;
18
19import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
20import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
21import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
22import static com.android.documentsui.model.DocumentInfo.getCursorLong;
23import static com.android.documentsui.model.DocumentInfo.getCursorString;
24
25import android.database.AbstractCursor;
26import android.database.Cursor;
27import android.os.Bundle;
28import android.provider.DocumentsContract.Document;
29
30import com.android.documentsui.model.DocumentInfo;
31
32/**
33 * Cursor wrapper that presents a sorted view of the underlying cursor. Handles
34 * common {@link Document} sorting modes, such as ordering directories first.
35 */
36public class SortingCursorWrapper extends AbstractCursor {
37    private final Cursor mCursor;
38
39    private final int[] mPosition;
40    private final String[] mValueString;
41    private final long[] mValueLong;
42
43    public SortingCursorWrapper(Cursor cursor, int sortOrder) {
44        mCursor = cursor;
45
46        final int count = cursor.getCount();
47        mPosition = new int[count];
48        switch (sortOrder) {
49            case SORT_ORDER_DISPLAY_NAME:
50                mValueString = new String[count];
51                mValueLong = null;
52                break;
53            case SORT_ORDER_LAST_MODIFIED:
54            case SORT_ORDER_SIZE:
55                mValueString = null;
56                mValueLong = new long[count];
57                break;
58            default:
59                throw new IllegalArgumentException();
60        }
61
62        cursor.moveToPosition(-1);
63        for (int i = 0; i < count; i++) {
64            cursor.moveToNext();
65            mPosition[i] = i;
66
67            switch (sortOrder) {
68                case SORT_ORDER_DISPLAY_NAME:
69                    final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
70                    final String displayName = getCursorString(
71                            cursor, Document.COLUMN_DISPLAY_NAME);
72                    if (Document.MIME_TYPE_DIR.equals(mimeType)) {
73                        mValueString[i] = DocumentInfo.DIR_PREFIX + displayName;
74                    } else {
75                        mValueString[i] = displayName;
76                    }
77                    break;
78                case SORT_ORDER_LAST_MODIFIED:
79                    mValueLong[i] = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
80                    break;
81                case SORT_ORDER_SIZE:
82                    mValueLong[i] = getCursorLong(cursor, Document.COLUMN_SIZE);
83                    break;
84            }
85        }
86
87        switch (sortOrder) {
88            case SORT_ORDER_DISPLAY_NAME:
89                synchronized (SortingCursorWrapper.class) {
90
91                    binarySort(mPosition, mValueString);
92                }
93                break;
94            case SORT_ORDER_LAST_MODIFIED:
95            case SORT_ORDER_SIZE:
96                binarySort(mPosition, mValueLong);
97                break;
98        }
99    }
100
101    @Override
102    public Bundle getExtras() {
103        return mCursor.getExtras();
104    }
105
106    @Override
107    public void close() {
108        super.close();
109        mCursor.close();
110    }
111
112    @Override
113    public boolean onMove(int oldPosition, int newPosition) {
114        return mCursor.moveToPosition(mPosition[newPosition]);
115    }
116
117    @Override
118    public String[] getColumnNames() {
119        return mCursor.getColumnNames();
120    }
121
122    @Override
123    public int getCount() {
124        return mCursor.getCount();
125    }
126
127    @Override
128    public double getDouble(int column) {
129        return mCursor.getDouble(column);
130    }
131
132    @Override
133    public float getFloat(int column) {
134        return mCursor.getFloat(column);
135    }
136
137    @Override
138    public int getInt(int column) {
139        return mCursor.getInt(column);
140    }
141
142    @Override
143    public long getLong(int column) {
144        return mCursor.getLong(column);
145    }
146
147    @Override
148    public short getShort(int column) {
149        return mCursor.getShort(column);
150    }
151
152    @Override
153    public String getString(int column) {
154        return mCursor.getString(column);
155    }
156
157    @Override
158    public int getType(int column) {
159        return mCursor.getType(column);
160    }
161
162    @Override
163    public boolean isNull(int column) {
164        return mCursor.isNull(column);
165    }
166
167    /**
168     * Borrowed from TimSort.binarySort(), but modified to sort two column
169     * dataset.
170     */
171    private static void binarySort(int[] position, String[] value) {
172        final int count = position.length;
173        for (int start = 1; start < count; start++) {
174            final int pivotPosition = position[start];
175            final String pivotValue = value[start];
176
177            int left = 0;
178            int right = start;
179
180            while (left < right) {
181                int mid = (left + right) >>> 1;
182
183                final String lhs = pivotValue;
184                final String rhs = value[mid];
185                final int compare = DocumentInfo.compareToIgnoreCaseNullable(lhs, rhs);
186
187                if (compare < 0) {
188                    right = mid;
189                } else {
190                    left = mid + 1;
191                }
192            }
193
194            int n = start - left;
195            switch (n) {
196                case 2:
197                    position[left + 2] = position[left + 1];
198                    value[left + 2] = value[left + 1];
199                case 1:
200                    position[left + 1] = position[left];
201                    value[left + 1] = value[left];
202                    break;
203                default:
204                    System.arraycopy(position, left, position, left + 1, n);
205                    System.arraycopy(value, left, value, left + 1, n);
206            }
207
208            position[left] = pivotPosition;
209            value[left] = pivotValue;
210        }
211    }
212
213    /**
214     * Borrowed from TimSort.binarySort(), but modified to sort two column
215     * dataset.
216     */
217    private static void binarySort(int[] position, long[] value) {
218        final int count = position.length;
219        for (int start = 1; start < count; start++) {
220            final int pivotPosition = position[start];
221            final long pivotValue = value[start];
222
223            int left = 0;
224            int right = start;
225
226            while (left < right) {
227                int mid = (left + right) >>> 1;
228
229                final long lhs = pivotValue;
230                final long rhs = value[mid];
231                final int compare = Long.compare(lhs, rhs);
232                if (compare > 0) {
233                    right = mid;
234                } else {
235                    left = mid + 1;
236                }
237            }
238
239            int n = start - left;
240            switch (n) {
241                case 2:
242                    position[left + 2] = position[left + 1];
243                    value[left + 2] = value[left + 1];
244                case 1:
245                    position[left + 1] = position[left];
246                    value[left + 1] = value[left];
247                    break;
248                default:
249                    System.arraycopy(position, left, position, left + 1, n);
250                    System.arraycopy(value, left, value, left + 1, n);
251            }
252
253            position[left] = pivotPosition;
254            value[left] = pivotValue;
255        }
256    }
257}
258