Home | History | Annotate | Download | only in content
      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