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 */ 14package androidx.leanback.widget; 15 16import android.database.Cursor; 17import android.util.LruCache; 18 19import androidx.leanback.database.CursorMapper; 20 21/** 22 * An {@link ObjectAdapter} implemented with a {@link Cursor}. 23 */ 24public class CursorObjectAdapter extends ObjectAdapter { 25 private static final int CACHE_SIZE = 100; 26 private Cursor mCursor; 27 private CursorMapper mMapper; 28 private final LruCache<Integer, Object> mItemCache = new LruCache<Integer, Object>(CACHE_SIZE); 29 30 /** 31 * Constructs an adapter with the given {@link PresenterSelector}. 32 */ 33 public CursorObjectAdapter(PresenterSelector presenterSelector) { 34 super(presenterSelector); 35 } 36 37 /** 38 * Constructs an adapter that uses the given {@link Presenter} for all items. 39 */ 40 public CursorObjectAdapter(Presenter presenter) { 41 super(presenter); 42 } 43 44 /** 45 * Constructs an adapter. 46 */ 47 public CursorObjectAdapter() { 48 super(); 49 } 50 51 /** 52 * Changes the underlying cursor to a new cursor. If there is 53 * an existing cursor it will be closed if it is different than the new 54 * cursor. 55 * 56 * @param cursor The new cursor to be used. 57 */ 58 public void changeCursor(Cursor cursor) { 59 if (cursor == mCursor) { 60 return; 61 } 62 if (mCursor != null) { 63 mCursor.close(); 64 } 65 mCursor = cursor; 66 mItemCache.trimToSize(0); 67 onCursorChanged(); 68 } 69 70 /** 71 * Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor), 72 * the returned old Cursor is not closed. 73 * 74 * @param cursor The new cursor to be used. 75 */ 76 public Cursor swapCursor(Cursor cursor) { 77 if (cursor == mCursor) { 78 return mCursor; 79 } 80 Cursor oldCursor = mCursor; 81 mCursor = cursor; 82 mItemCache.trimToSize(0); 83 onCursorChanged(); 84 return oldCursor; 85 } 86 87 /** 88 * Called whenever the cursor changes. 89 */ 90 protected void onCursorChanged() { 91 notifyChanged(); 92 } 93 94 /** 95 * Returns the {@link Cursor} backing the adapter. 96 */ 97 public final Cursor getCursor() { 98 return mCursor; 99 } 100 101 /** 102 * Sets the {@link CursorMapper} used to convert {@link Cursor} rows into 103 * Objects. 104 */ 105 public final void setMapper(CursorMapper mapper) { 106 boolean changed = mMapper != mapper; 107 mMapper = mapper; 108 109 if (changed) { 110 onMapperChanged(); 111 } 112 } 113 114 /** 115 * Called when {@link #setMapper(CursorMapper)} is called and a different 116 * mapper is provided. 117 */ 118 protected void onMapperChanged() { 119 } 120 121 /** 122 * Returns the {@link CursorMapper} used to convert {@link Cursor} rows into 123 * Objects. 124 */ 125 public final CursorMapper getMapper() { 126 return mMapper; 127 } 128 129 @Override 130 public int size() { 131 if (mCursor == null) { 132 return 0; 133 } 134 return mCursor.getCount(); 135 } 136 137 @Override 138 public Object get(int index) { 139 if (mCursor == null) { 140 return null; 141 } 142 if (!mCursor.moveToPosition(index)) { 143 throw new ArrayIndexOutOfBoundsException(); 144 } 145 Object item = mItemCache.get(index); 146 if (item != null) { 147 return item; 148 } 149 item = mMapper.convert(mCursor); 150 mItemCache.put(index, item); 151 return item; 152 } 153 154 /** 155 * Closes this adapter, closing the backing {@link Cursor} as well. 156 */ 157 public void close() { 158 if (mCursor != null) { 159 mCursor.close(); 160 mCursor = null; 161 } 162 } 163 164 /** 165 * Returns true if the adapter, and hence the backing {@link Cursor}, is closed; false 166 * otherwise. 167 */ 168 public boolean isClosed() { 169 return mCursor == null || mCursor.isClosed(); 170 } 171 172 /** 173 * Removes an item from the cache. This will force the item to be re-read 174 * from the data source the next time {@link #get(int)} is called. 175 */ 176 protected final void invalidateCache(int index) { 177 mItemCache.remove(index); 178 } 179 180 /** 181 * Removes {@code count} items starting at {@code index}. 182 */ 183 protected final void invalidateCache(int index, int count) { 184 for (int limit = count + index; index < limit; index++) { 185 invalidateCache(index); 186 } 187 } 188 189 @Override 190 public boolean isImmediateNotifySupported() { 191 return true; 192 } 193} 194