1 /* 2 * Copyright (C) 2007 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 17 package com.android.internal.database; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.database.AbstractCursor; 21 import android.database.Cursor; 22 import android.database.DataSetObserver; 23 import android.util.Log; 24 25 /** 26 * A variant of MergeCursor that sorts the cursors being merged. If decent 27 * performance is ever obtained, it can be put back under android.database. 28 */ 29 public class SortCursor extends AbstractCursor 30 { 31 private static final String TAG = "SortCursor"; 32 @UnsupportedAppUsage 33 private Cursor mCursor; // updated in onMove 34 @UnsupportedAppUsage 35 private Cursor[] mCursors; 36 private int [] mSortColumns; 37 private final int ROWCACHESIZE = 64; 38 private int mRowNumCache[] = new int[ROWCACHESIZE]; 39 private int mCursorCache[] = new int[ROWCACHESIZE]; 40 private int mCurRowNumCache[][]; 41 private int mLastCacheHit = -1; 42 43 private DataSetObserver mObserver = new DataSetObserver() { 44 45 @Override 46 public void onChanged() { 47 // Reset our position so the optimizations in move-related code 48 // don't screw us over 49 mPos = -1; 50 } 51 52 @Override 53 public void onInvalidated() { 54 mPos = -1; 55 } 56 }; 57 58 @UnsupportedAppUsage SortCursor(Cursor[] cursors, String sortcolumn)59 public SortCursor(Cursor[] cursors, String sortcolumn) 60 { 61 mCursors = cursors; 62 63 int length = mCursors.length; 64 mSortColumns = new int[length]; 65 for (int i = 0 ; i < length ; i++) { 66 if (mCursors[i] == null) continue; 67 68 // Register ourself as a data set observer 69 mCursors[i].registerDataSetObserver(mObserver); 70 71 mCursors[i].moveToFirst(); 72 73 // We don't catch the exception 74 mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn); 75 } 76 mCursor = null; 77 String smallest = ""; 78 for (int j = 0 ; j < length; j++) { 79 if (mCursors[j] == null || mCursors[j].isAfterLast()) 80 continue; 81 String current = mCursors[j].getString(mSortColumns[j]); 82 if (mCursor == null || current.compareToIgnoreCase(smallest) < 0) { 83 smallest = current; 84 mCursor = mCursors[j]; 85 } 86 } 87 88 for (int i = mRowNumCache.length - 1; i >= 0; i--) { 89 mRowNumCache[i] = -2; 90 } 91 mCurRowNumCache = new int[ROWCACHESIZE][length]; 92 } 93 94 @Override getCount()95 public int getCount() 96 { 97 int count = 0; 98 int length = mCursors.length; 99 for (int i = 0 ; i < length ; i++) { 100 if (mCursors[i] != null) { 101 count += mCursors[i].getCount(); 102 } 103 } 104 return count; 105 } 106 107 @Override onMove(int oldPosition, int newPosition)108 public boolean onMove(int oldPosition, int newPosition) 109 { 110 if (oldPosition == newPosition) 111 return true; 112 113 /* Find the right cursor 114 * Because the client of this cursor (the listadapter/view) tends 115 * to jump around in the cursor somewhat, a simple cache strategy 116 * is used to avoid having to search all cursors from the start. 117 * TODO: investigate strategies for optimizing random access and 118 * reverse-order access. 119 */ 120 121 int cache_entry = newPosition % ROWCACHESIZE; 122 123 if (mRowNumCache[cache_entry] == newPosition) { 124 int which = mCursorCache[cache_entry]; 125 mCursor = mCursors[which]; 126 if (mCursor == null) { 127 Log.w(TAG, "onMove: cache results in a null cursor."); 128 return false; 129 } 130 mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]); 131 mLastCacheHit = cache_entry; 132 return true; 133 } 134 135 mCursor = null; 136 int length = mCursors.length; 137 138 if (mLastCacheHit >= 0) { 139 for (int i = 0; i < length; i++) { 140 if (mCursors[i] == null) continue; 141 mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]); 142 } 143 } 144 145 if (newPosition < oldPosition || oldPosition == -1) { 146 for (int i = 0 ; i < length; i++) { 147 if (mCursors[i] == null) continue; 148 mCursors[i].moveToFirst(); 149 } 150 oldPosition = 0; 151 } 152 if (oldPosition < 0) { 153 oldPosition = 0; 154 } 155 156 // search forward to the new position 157 int smallestIdx = -1; 158 for(int i = oldPosition; i <= newPosition; i++) { 159 String smallest = ""; 160 smallestIdx = -1; 161 for (int j = 0 ; j < length; j++) { 162 if (mCursors[j] == null || mCursors[j].isAfterLast()) { 163 continue; 164 } 165 String current = mCursors[j].getString(mSortColumns[j]); 166 if (smallestIdx < 0 || current.compareToIgnoreCase(smallest) < 0) { 167 smallest = current; 168 smallestIdx = j; 169 } 170 } 171 if (i == newPosition) break; 172 if (mCursors[smallestIdx] != null) { 173 mCursors[smallestIdx].moveToNext(); 174 } 175 } 176 mCursor = mCursors[smallestIdx]; 177 mRowNumCache[cache_entry] = newPosition; 178 mCursorCache[cache_entry] = smallestIdx; 179 for (int i = 0; i < length; i++) { 180 if (mCursors[i] != null) { 181 mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition(); 182 } 183 } 184 mLastCacheHit = -1; 185 return true; 186 } 187 188 @Override getString(int column)189 public String getString(int column) 190 { 191 return mCursor.getString(column); 192 } 193 194 @Override getShort(int column)195 public short getShort(int column) 196 { 197 return mCursor.getShort(column); 198 } 199 200 @Override getInt(int column)201 public int getInt(int column) 202 { 203 return mCursor.getInt(column); 204 } 205 206 @Override getLong(int column)207 public long getLong(int column) 208 { 209 return mCursor.getLong(column); 210 } 211 212 @Override getFloat(int column)213 public float getFloat(int column) 214 { 215 return mCursor.getFloat(column); 216 } 217 218 @Override getDouble(int column)219 public double getDouble(int column) 220 { 221 return mCursor.getDouble(column); 222 } 223 224 @Override getType(int column)225 public int getType(int column) { 226 return mCursor.getType(column); 227 } 228 229 @Override isNull(int column)230 public boolean isNull(int column) 231 { 232 return mCursor.isNull(column); 233 } 234 235 @Override getBlob(int column)236 public byte[] getBlob(int column) 237 { 238 return mCursor.getBlob(column); 239 } 240 241 @Override getColumnNames()242 public String[] getColumnNames() 243 { 244 if (mCursor != null) { 245 return mCursor.getColumnNames(); 246 } else { 247 // All of the cursors may be empty, but they can still return 248 // this information. 249 int length = mCursors.length; 250 for (int i = 0 ; i < length ; i++) { 251 if (mCursors[i] != null) { 252 return mCursors[i].getColumnNames(); 253 } 254 } 255 throw new IllegalStateException("No cursor that can return names"); 256 } 257 } 258 259 @Override deactivate()260 public void deactivate() 261 { 262 int length = mCursors.length; 263 for (int i = 0 ; i < length ; i++) { 264 if (mCursors[i] == null) continue; 265 mCursors[i].deactivate(); 266 } 267 } 268 269 @Override close()270 public void close() { 271 int length = mCursors.length; 272 for (int i = 0 ; i < length ; i++) { 273 if (mCursors[i] == null) continue; 274 mCursors[i].close(); 275 } 276 } 277 278 @Override registerDataSetObserver(DataSetObserver observer)279 public void registerDataSetObserver(DataSetObserver observer) { 280 int length = mCursors.length; 281 for (int i = 0 ; i < length ; i++) { 282 if (mCursors[i] != null) { 283 mCursors[i].registerDataSetObserver(observer); 284 } 285 } 286 } 287 288 @Override unregisterDataSetObserver(DataSetObserver observer)289 public void unregisterDataSetObserver(DataSetObserver observer) { 290 int length = mCursors.length; 291 for (int i = 0 ; i < length ; i++) { 292 if (mCursors[i] != null) { 293 mCursors[i].unregisterDataSetObserver(observer); 294 } 295 } 296 } 297 298 @Override requery()299 public boolean requery() 300 { 301 int length = mCursors.length; 302 for (int i = 0 ; i < length ; i++) { 303 if (mCursors[i] == null) continue; 304 305 if (mCursors[i].requery() == false) { 306 return false; 307 } 308 } 309 310 return true; 311 } 312 } 313