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