1 /* 2 * Copyright (C) 2017 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 androidx.contentpager.content; 18 19 import static androidx.core.util.Preconditions.checkArgument; 20 21 import android.database.AbstractCursor; 22 import android.database.ContentObserver; 23 import android.database.Cursor; 24 import android.database.CursorIndexOutOfBoundsException; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 29 import androidx.annotation.RestrictTo; 30 31 /** 32 * A {@link Cursor} implementation that stores information in-memory, in a type-safe fashion. 33 * Values are stored, when possible, as primitives to avoid the need for the autoboxing (as is 34 * necessary when working with MatrixCursor). 35 * 36 * <p>Unlike {@link android.database.MatrixCursor}, this cursor is not mutable at runtime. 37 * It exists solely as a destination for data copied by {@link ContentPager} from a source 38 * Cursor when a page is being synthesized. It is not anticipated at this time that this 39 * will be useful outside of this package. As such it is immutable once constructed. 40 * @hide 41 */ 42 @RestrictTo(RestrictTo.Scope.LIBRARY) 43 final class InMemoryCursor extends AbstractCursor { 44 45 private static final int NUM_TYPES = 5; 46 47 private final String[] mColumnNames; 48 private final int mRowCount; 49 50 // This is an index of column, by type. Maps back to position 51 // in native array. 52 // E.g. if we have columns typed like [string, int, int, string, int, int] 53 // the values in this index will be: 54 // mTypedColumnIndex[string][0] == 0 55 // mTypedColumnIndex[int][1] == 0 56 // mTypedColumnIndex[int][2] == 1 57 // mTypedColumnIndex[string][3] == 1 58 // mTypedColumnIndex[int][4] == 2 59 // mTypedColumnIndex[int][4] == 3 60 // This allows us to calculate the number of cells by type in a row 61 // which, in turn, allows us to calculate the size of the primitive storage arrays. 62 // We also use this information to lookup a particular typed value given 63 // the row offset and column offset. 64 private final int[][] mTypedColumnIndex; 65 66 // simple index to column to type. 67 private final int[] mColumnType; 68 69 // count of number of columns by type. 70 private final int[] mColumnTypeCount; 71 72 private final Bundle mExtras; 73 74 private final ObserverRelay mObserverRelay; 75 76 // Row data decomposed by type. 77 private long[] mLongs; 78 private double[] mDoubles; 79 private byte[][] mBlobs; 80 private String[] mStrings; 81 82 /** 83 * @param cursor source of data to copy. Ownership is reserved to the called, meaning 84 * we won't ever close it. 85 */ 86 InMemoryCursor(Cursor cursor, int offset, int length, int disposition) { 87 checkArgument(offset < cursor.getCount()); 88 89 // NOTE: The cursor could simply be saved to a field, but we choose to wrap 90 // in a dedicated relay class to avoid hanging directly onto a reference 91 // to the cursor...so future authors are not enticed to think there's 92 // a live link between the delegate cursor and this cursor. 93 mObserverRelay = new ObserverRelay(cursor); 94 95 mColumnNames = cursor.getColumnNames(); 96 mRowCount = Math.min(length, cursor.getCount() - offset); 97 int numColumns = cursor.getColumnCount(); 98 99 mExtras = ContentPager.buildExtras(cursor.getExtras(), cursor.getCount(), disposition); 100 101 mColumnType = new int[numColumns]; 102 mTypedColumnIndex = new int[NUM_TYPES][numColumns]; 103 mColumnTypeCount = new int[NUM_TYPES]; 104 105 if (!cursor.moveToFirst()) { 106 throw new RuntimeException("Can't position cursor to first row."); 107 } 108 109 for (int col = 0; col < numColumns; col++) { 110 int type = cursor.getType(col); 111 mColumnType[col] = type; 112 mTypedColumnIndex[type][col] = mColumnTypeCount[type]++; 113 } 114 115 mLongs = new long[mRowCount * mColumnTypeCount[FIELD_TYPE_INTEGER]]; 116 mDoubles = new double[mRowCount * mColumnTypeCount[FIELD_TYPE_FLOAT]]; 117 mBlobs = new byte[mRowCount * mColumnTypeCount[FIELD_TYPE_BLOB]][]; 118 mStrings = new String[mRowCount * mColumnTypeCount[FIELD_TYPE_STRING]]; 119 120 for (int row = 0; row < mRowCount; row++) { 121 if (!cursor.moveToPosition(offset + row)) { 122 throw new RuntimeException("Unable to position cursor."); 123 } 124 125 // Now copy data from the row into primitive arrays. 126 for (int col = 0; col < mColumnType.length; col++) { 127 int type = mColumnType[col]; 128 int position = getCellPosition(row, col, type); 129 130 switch(type) { 131 case FIELD_TYPE_NULL: 132 throw new UnsupportedOperationException("Not implemented."); 133 case FIELD_TYPE_INTEGER: 134 mLongs[position] = cursor.getLong(col); 135 break; 136 case FIELD_TYPE_FLOAT: 137 mDoubles[position] = cursor.getDouble(col); 138 break; 139 case FIELD_TYPE_BLOB: 140 mBlobs[position] = cursor.getBlob(col); 141 break; 142 case FIELD_TYPE_STRING: 143 mStrings[position] = cursor.getString(col); 144 break; 145 } 146 } 147 } 148 } 149 150 @Override 151 public Bundle getExtras() { 152 return mExtras; 153 } 154 155 // Returns the "cell" position for a specific row+column+type. 156 private int getCellPosition(int row, int col, int type) { 157 return (row * mColumnTypeCount[type]) + mTypedColumnIndex[type][col]; 158 } 159 160 @Override 161 public int getCount() { 162 return mRowCount; 163 } 164 165 @Override 166 public String[] getColumnNames() { 167 return mColumnNames; 168 } 169 170 @Override 171 public short getShort(int column) { 172 checkValidColumn(column); 173 checkValidPosition(); 174 return (short) mLongs[getCellPosition(getPosition(), column, FIELD_TYPE_INTEGER)]; 175 } 176 177 @Override 178 public int getInt(int column) { 179 checkValidColumn(column); 180 checkValidPosition(); 181 return (int) mLongs[getCellPosition(getPosition(), column, FIELD_TYPE_INTEGER)]; 182 } 183 184 @Override 185 public long getLong(int column) { 186 checkValidColumn(column); 187 checkValidPosition(); 188 return mLongs[getCellPosition(getPosition(), column, FIELD_TYPE_INTEGER)]; 189 } 190 191 @Override 192 public float getFloat(int column) { 193 checkValidColumn(column); 194 checkValidPosition(); 195 return (float) mDoubles[getCellPosition(getPosition(), column, FIELD_TYPE_FLOAT)]; 196 } 197 198 @Override 199 public double getDouble(int column) { 200 checkValidColumn(column); 201 checkValidPosition(); 202 return mDoubles[getCellPosition(getPosition(), column, FIELD_TYPE_FLOAT)]; 203 } 204 205 @Override 206 public byte[] getBlob(int column) { 207 checkValidColumn(column); 208 checkValidPosition(); 209 return mBlobs[getCellPosition(getPosition(), column, FIELD_TYPE_BLOB)]; 210 } 211 212 @Override 213 public String getString(int column) { 214 checkValidColumn(column); 215 checkValidPosition(); 216 return mStrings[getCellPosition(getPosition(), column, FIELD_TYPE_STRING)]; 217 } 218 219 @Override 220 public int getType(int column) { 221 checkValidColumn(column); 222 return mColumnType[column]; 223 } 224 225 @Override 226 public boolean isNull(int column) { 227 checkValidColumn(column); 228 switch (mColumnType[column]) { 229 case FIELD_TYPE_STRING: 230 return getString(column) != null; 231 case FIELD_TYPE_BLOB: 232 return getBlob(column) != null; 233 default: 234 return false; 235 } 236 } 237 238 private void checkValidPosition() { 239 if (getPosition() < 0) { 240 throw new CursorIndexOutOfBoundsException("Before first row."); 241 } 242 if (getPosition() >= mRowCount) { 243 throw new CursorIndexOutOfBoundsException("After last row."); 244 } 245 } 246 247 private void checkValidColumn(int column) { 248 if (column < 0 || column >= mColumnNames.length) { 249 throw new CursorIndexOutOfBoundsException( 250 "Requested column: " + column + ", # of columns: " + mColumnNames.length); 251 } 252 } 253 254 @Override 255 public void registerContentObserver(ContentObserver observer) { 256 mObserverRelay.registerContentObserver(observer); 257 } 258 259 @Override 260 public void unregisterContentObserver(ContentObserver observer) { 261 mObserverRelay.unregisterContentObserver(observer); 262 } 263 264 private static class ObserverRelay extends ContentObserver { 265 private final Cursor mCursor; 266 267 ObserverRelay(Cursor cursor) { 268 super(new Handler(Looper.getMainLooper())); 269 mCursor = cursor; 270 } 271 272 void registerContentObserver(ContentObserver observer) { 273 mCursor.registerContentObserver(observer); 274 } 275 276 void unregisterContentObserver(ContentObserver observer) { 277 mCursor.unregisterContentObserver(observer); 278 } 279 } 280 } 281