1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package android.support.v17.leanback.widget; 15 16 import android.database.Cursor; 17 import android.support.v17.leanback.database.CursorMapper; 18 import android.util.LruCache; 19 20 /** 21 * An ObjectAdapter implemented with a {@link Cursor}. 22 */ 23 public class CursorObjectAdapter extends ObjectAdapter { 24 private static final int CACHE_SIZE = 100; 25 private Cursor mCursor; 26 private CursorMapper mMapper; 27 private final LruCache<Integer, Object> mItemCache = new LruCache<Integer, Object>(CACHE_SIZE); 28 29 /** 30 * Construct an adapter with the given {@link PresenterSelector}. 31 */ CursorObjectAdapter(PresenterSelector presenterSelector)32 public CursorObjectAdapter(PresenterSelector presenterSelector) { 33 super(presenterSelector); 34 } 35 36 /** 37 * Construct an adapter that uses the given {@link Presenter} for all items. 38 */ CursorObjectAdapter(Presenter presenter)39 public CursorObjectAdapter(Presenter presenter) { 40 super(presenter); 41 } 42 43 /** 44 * Construct an adapter. 45 */ CursorObjectAdapter()46 public CursorObjectAdapter() { 47 super(); 48 } 49 50 /** 51 * Change the underlying cursor to a new cursor. If there is 52 * an existing cursor it will be closed if it is different than the new 53 * cursor. 54 * 55 * @param cursor The new cursor to be used. 56 */ changeCursor(Cursor cursor)57 public void changeCursor(Cursor cursor) { 58 if (cursor == mCursor) { 59 return; 60 } 61 if (mCursor != null) { 62 mCursor.close(); 63 } 64 mCursor = cursor; 65 mItemCache.trimToSize(0); 66 onCursorChanged(); 67 } 68 69 /** 70 * Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor), 71 * the returned old Cursor is not closed. 72 * 73 * @param cursor The new cursor to be used. 74 */ swapCursor(Cursor cursor)75 public Cursor swapCursor(Cursor cursor) { 76 if (cursor == mCursor) { 77 return mCursor; 78 } 79 Cursor oldCursor = mCursor; 80 mCursor = cursor; 81 mItemCache.trimToSize(0); 82 onCursorChanged(); 83 return oldCursor; 84 } 85 86 /** 87 * Called whenever the cursor changes. 88 */ onCursorChanged()89 protected void onCursorChanged() { 90 notifyChanged(); 91 } 92 93 /** 94 * Gets the {@link Cursor} backing the adapter. 95 */ getCursor()96 public final Cursor getCursor() { 97 return mCursor; 98 } 99 100 /** 101 * Sets the {@link CursorMapper} used to convert {@link Cursor} rows into 102 * Objects. 103 */ setMapper(CursorMapper mapper)104 public final void setMapper(CursorMapper mapper) { 105 boolean changed = mMapper != mapper; 106 mMapper = mapper; 107 108 if (changed) { 109 onMapperChanged(); 110 } 111 } 112 113 /** 114 * Called when {@link #setMapper(CursorMapper)} is called and a different 115 * mapper is provided. 116 */ onMapperChanged()117 protected void onMapperChanged() { 118 } 119 120 /** 121 * Gets the {@link CursorMapper} used to convert {@link Cursor} rows into 122 * Objects. 123 */ getMapper()124 public final CursorMapper getMapper() { 125 return mMapper; 126 } 127 128 @Override size()129 public int size() { 130 if (mCursor == null) { 131 return 0; 132 } 133 return mCursor.getCount(); 134 } 135 136 @Override get(int index)137 public Object get(int index) { 138 if (mCursor == null) { 139 return null; 140 } 141 if (!mCursor.moveToPosition(index)) { 142 throw new ArrayIndexOutOfBoundsException(); 143 } 144 Object item = mItemCache.get(index); 145 if (item != null) { 146 return item; 147 } 148 item = mMapper.convert(mCursor); 149 mItemCache.put(index, item); 150 return item; 151 } 152 153 /** 154 * Closes this adapter, closing the backing {@link Cursor} as well. 155 */ close()156 public void close() { 157 if (mCursor != null) { 158 mCursor.close(); 159 mCursor = null; 160 } 161 } 162 163 /** 164 * Checks whether the adapter, and hence the backing {@link Cursor}, is closed. 165 */ isClosed()166 public boolean isClosed() { 167 return mCursor == null || mCursor.isClosed(); 168 } 169 170 /** 171 * Remove an item from the cache. This will force the item to be re-read 172 * from the data source the next time (@link #get(int)} is called. 173 */ invalidateCache(int index)174 protected final void invalidateCache(int index) { 175 mItemCache.remove(index); 176 } 177 178 /** 179 * Remove {@code count} items starting at {@code index}. 180 */ invalidateCache(int index, int count)181 protected final void invalidateCache(int index, int count) { 182 for (int limit = count + index; index < limit; index++) { 183 invalidateCache(index); 184 } 185 } 186 } 187