1 /* 2 * Copyright (C) 2015 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.example.android.tvleanback.data; 18 19 import android.database.AbstractCursor; 20 import android.database.Cursor; 21 22 /** 23 * A sample paginated cursor which will pre-fetch and cache rows. 24 */ 25 public class PaginatedCursor extends AbstractCursor { 26 /** 27 * The number of items that should be loaded each time. 28 */ 29 private static final int PAGE_SIZE = 10; 30 31 /** 32 * The threshold of number of items left that a new page should be loaded. 33 */ 34 private static final int PAGE_THRESHOLD = PAGE_SIZE / 2; 35 36 private final Cursor mCursor; 37 private final int mRowCount; 38 private final boolean[] mCachedRows; 39 private final String[] mColumnNames; 40 private final int mColumnCount; 41 private final int[] mColumnTypes; 42 43 private final byte[][][] mByteArrayDataCache; 44 private final float[][] mFloatDataCache; 45 private final int[][] mIntDataCache; 46 private final String[][] mStringDataCache; 47 /** 48 * Index mapping from column index into the data type specific cache index; 49 */ 50 private final int[] mByteArrayCacheIndexMap; 51 private final int[] mFloatCacheIndexMap; 52 private final int[] mIntCacheIndexMap; 53 private final int[] mStringCacheIndexMap; 54 private int mByteArrayCacheColumnSize; 55 private int mFloatCacheColumnSize; 56 private int mIntCacheColumnSize; 57 private int mStringCacheColumnSize; 58 private int mLastCachePosition; 59 PaginatedCursor(Cursor cursor)60 public PaginatedCursor(Cursor cursor) { 61 super(); 62 mCursor = cursor; 63 mRowCount = mCursor.getCount(); 64 mCachedRows = new boolean[mRowCount]; 65 mColumnNames = mCursor.getColumnNames(); 66 mColumnCount = mCursor.getColumnCount(); 67 mColumnTypes = new int[mColumnCount]; 68 69 mByteArrayCacheColumnSize = 0; 70 mFloatCacheColumnSize = 0; 71 mIntCacheColumnSize = 0; 72 mStringCacheColumnSize = 0; 73 74 mByteArrayCacheIndexMap = new int[mColumnCount]; 75 mFloatCacheIndexMap = new int[mColumnCount]; 76 mIntCacheIndexMap = new int[mColumnCount]; 77 mStringCacheIndexMap = new int[mColumnCount]; 78 79 mCursor.moveToFirst(); 80 for (int i = 0; i < mColumnCount; i++) { 81 int type = mCursor.getType(i); 82 mColumnTypes[i] = type; 83 switch (type) { 84 case Cursor.FIELD_TYPE_BLOB: 85 mByteArrayCacheIndexMap[i] = mByteArrayCacheColumnSize++; 86 break; 87 case Cursor.FIELD_TYPE_FLOAT: 88 mFloatCacheIndexMap[i] = mFloatCacheColumnSize++; 89 break; 90 case Cursor.FIELD_TYPE_INTEGER: 91 mIntCacheIndexMap[i] = mIntCacheColumnSize++; 92 break; 93 case Cursor.FIELD_TYPE_STRING: 94 mStringCacheIndexMap[i] = mStringCacheColumnSize++; 95 break; 96 } 97 } 98 99 mByteArrayDataCache = mByteArrayCacheColumnSize > 0 ? new byte[mRowCount][][] : null; 100 mFloatDataCache = mFloatCacheColumnSize > 0 ? new float[mRowCount][] : null; 101 mIntDataCache = mIntCacheColumnSize > 0 ? new int[mRowCount][] : null; 102 mStringDataCache = mStringCacheColumnSize > 0 ? new String[mRowCount][] : null; 103 104 for (int i = 0; i < mRowCount; i++) { 105 mCachedRows[i] = false; 106 if (mByteArrayDataCache != null) { 107 mByteArrayDataCache[i] = new byte[mByteArrayCacheColumnSize][]; 108 } 109 if (mFloatDataCache != null) { 110 mFloatDataCache[i] = new float[mFloatCacheColumnSize]; 111 } 112 if (mIntDataCache != null) { 113 mIntDataCache[i] = new int[mIntCacheColumnSize]; 114 } 115 if (mStringDataCache != null) { 116 mStringDataCache[i] = new String[mStringCacheColumnSize]; 117 } 118 } 119 120 // Cache at the initialization stage. 121 loadCacheStartingFromPosition(0); 122 } 123 124 /** 125 * Try to load un-cached data with size {@link PAGE_SIZE} starting from given index. 126 */ loadCacheStartingFromPosition(int index)127 private void loadCacheStartingFromPosition(int index) { 128 mCursor.moveToPosition(index); 129 for (int row = index; row < (index + PAGE_SIZE) && row < mRowCount; row++) { 130 if (!mCachedRows[row]) { 131 for (int col = 0; col < mColumnCount; col++) { 132 switch (mCursor.getType(col)) { 133 case Cursor.FIELD_TYPE_BLOB: 134 mByteArrayDataCache[row][mByteArrayCacheIndexMap[col]] = 135 mCursor.getBlob(col); 136 break; 137 case Cursor.FIELD_TYPE_FLOAT: 138 mFloatDataCache[row][mFloatCacheIndexMap[col]] = mCursor.getFloat(col); 139 break; 140 case Cursor.FIELD_TYPE_INTEGER: 141 mIntDataCache[row][mIntCacheIndexMap[col]] = mCursor.getInt(col); 142 break; 143 case Cursor.FIELD_TYPE_STRING: 144 mStringDataCache[row][mStringCacheIndexMap[col]] = 145 mCursor.getString(col); 146 break; 147 } 148 } 149 mCachedRows[row] = true; 150 } 151 mCursor.moveToNext(); 152 } 153 mLastCachePosition = Math.min(index + PAGE_SIZE, mRowCount) - 1; 154 } 155 156 @Override onMove(int oldPosition, int newPosition)157 public boolean onMove(int oldPosition, int newPosition) { 158 // If it's a consecutive move and haven't exceeds the threshold, do nothing. 159 if ((newPosition - oldPosition) != 1 || 160 (newPosition + PAGE_THRESHOLD) <= mLastCachePosition) { 161 loadCacheStartingFromPosition(newPosition); 162 } 163 return true; 164 } 165 166 @Override getType(int column)167 public int getType(int column) { 168 return mColumnTypes[column]; 169 } 170 171 @Override getCount()172 public int getCount() { 173 return mRowCount; 174 } 175 176 @Override getColumnNames()177 public String[] getColumnNames() { 178 return mColumnNames; 179 } 180 181 @Override getString(int column)182 public String getString(int column) { 183 return mStringDataCache[mPos][mStringCacheIndexMap[column]]; 184 } 185 186 @Override getShort(int column)187 public short getShort(int column) { 188 return (short) mIntDataCache[mPos][mIntCacheIndexMap[column]]; 189 } 190 191 @Override getInt(int column)192 public int getInt(int column) { 193 return mIntDataCache[mPos][mIntCacheIndexMap[column]]; 194 } 195 196 @Override getLong(int column)197 public long getLong(int column) { 198 return mIntDataCache[mPos][mIntCacheIndexMap[column]]; 199 } 200 201 @Override getFloat(int column)202 public float getFloat(int column) { 203 return mFloatDataCache[mPos][mFloatCacheIndexMap[column]]; 204 } 205 206 @Override getDouble(int column)207 public double getDouble(int column) { 208 return mFloatDataCache[mPos][mFloatCacheIndexMap[column]]; 209 } 210 211 @Override getBlob(int column)212 public byte[] getBlob(int column) { 213 return mByteArrayDataCache[mPos][mByteArrayCacheIndexMap[column]]; 214 } 215 216 @Override isNull(int column)217 public boolean isNull(int column) { 218 return mColumnTypes[column] == Cursor.FIELD_TYPE_NULL; 219 } 220 221 } 222