Home | History | Annotate | Download | only in widget
      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 androidx.leanback.widget;
     15 
     16 import android.database.Cursor;
     17 import android.util.LruCache;
     18 
     19 import androidx.leanback.database.CursorMapper;
     20 
     21 /**
     22  * An {@link ObjectAdapter} implemented with a {@link Cursor}.
     23  */
     24 public 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