Home | History | Annotate | Download | only in ui
      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.android.messaging.ui;
     18 
     19 import android.content.Context;
     20 import android.database.ContentObserver;
     21 import android.database.Cursor;
     22 import android.database.DataSetObserver;
     23 import android.os.Handler;
     24 import android.support.v7.widget.RecyclerView;
     25 import android.util.Log;
     26 import android.view.ViewGroup;
     27 import android.widget.FilterQueryProvider;
     28 
     29 /**
     30  * Copy of CursorAdapter suited for RecyclerView.
     31  *
     32  * TODO: BUG 16327984. Replace this with a framework supported CursorAdapter for
     33  * RecyclerView when one is available.
     34  */
     35 public abstract class CursorRecyclerAdapter<VH extends RecyclerView.ViewHolder>
     36         extends RecyclerView.Adapter<VH> {
     37     /**
     38      * This field should be made private, so it is hidden from the SDK.
     39      * {@hide}
     40      */
     41     protected boolean mDataValid;
     42     /**
     43      * This field should be made private, so it is hidden from the SDK.
     44      * {@hide}
     45      */
     46     protected boolean mAutoRequery;
     47     /**
     48      * This field should be made private, so it is hidden from the SDK.
     49      * {@hide}
     50      */
     51     protected Cursor mCursor;
     52     /**
     53      * This field should be made private, so it is hidden from the SDK.
     54      * {@hide}
     55      */
     56     protected Context mContext;
     57     /**
     58      * This field should be made private, so it is hidden from the SDK.
     59      * {@hide}
     60      */
     61     protected int mRowIDColumn;
     62     /**
     63      * This field should be made private, so it is hidden from the SDK.
     64      * {@hide}
     65      */
     66     protected ChangeObserver mChangeObserver;
     67     /**
     68      * This field should be made private, so it is hidden from the SDK.
     69      * {@hide}
     70      */
     71     protected DataSetObserver mDataSetObserver;
     72     /**
     73      * This field should be made private, so it is hidden from the SDK.
     74      * {@hide}
     75      */
     76     protected FilterQueryProvider mFilterQueryProvider;
     77 
     78     /**
     79      * If set the adapter will call requery() on the cursor whenever a content change
     80      * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
     81      *
     82      * @deprecated This option is discouraged, as it results in Cursor queries
     83      * being performed on the application's UI thread and thus can cause poor
     84      * responsiveness or even Application Not Responding errors.  As an alternative,
     85      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
     86      */
     87     @Deprecated
     88     public static final int FLAG_AUTO_REQUERY = 0x01;
     89 
     90     /**
     91      * If set the adapter will register a content observer on the cursor and will call
     92      * {@link #onContentChanged()} when a notification comes in.  Be careful when
     93      * using this flag: you will need to unset the current Cursor from the adapter
     94      * to avoid leaks due to its registered observers.  This flag is not needed
     95      * when using a CursorAdapter with a
     96      * {@link android.content.CursorLoader}.
     97      */
     98     public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
     99 
    100     /**
    101      * Recommended constructor.
    102      *
    103      * @param c The cursor from which to get the data.
    104      * @param context The context
    105      * @param flags Flags used to determine the behavior of the adapter; may
    106      * be any combination of {@link #FLAG_AUTO_REQUERY} and
    107      * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
    108      */
    109     public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) {
    110         init(context, c, flags);
    111     }
    112 
    113     void init(final Context context, final Cursor c, int flags) {
    114         if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
    115             flags |= FLAG_REGISTER_CONTENT_OBSERVER;
    116             mAutoRequery = true;
    117         } else {
    118             mAutoRequery = false;
    119         }
    120         final boolean cursorPresent = c != null;
    121         mCursor = c;
    122         mDataValid = cursorPresent;
    123         mContext = context;
    124         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
    125         if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
    126             mChangeObserver = new ChangeObserver();
    127             mDataSetObserver = new MyDataSetObserver();
    128         } else {
    129             mChangeObserver = null;
    130             mDataSetObserver = null;
    131         }
    132 
    133         if (cursorPresent) {
    134             if (mChangeObserver != null) {
    135                 c.registerContentObserver(mChangeObserver);
    136             }
    137             if (mDataSetObserver != null) {
    138                 c.registerDataSetObserver(mDataSetObserver);
    139             }
    140         }
    141     }
    142 
    143     /**
    144      * Returns the cursor.
    145      * @return the cursor.
    146      */
    147     public Cursor getCursor() {
    148         return mCursor;
    149     }
    150 
    151     @Override
    152     public int getItemCount() {
    153         if (mDataValid && mCursor != null) {
    154             return mCursor.getCount();
    155         } else {
    156             return 0;
    157         }
    158     }
    159 
    160     /**
    161      * @see android.support.v7.widget.RecyclerView.Adapter#getItem(int)
    162      */
    163     public Object getItem(final int position) {
    164         if (mDataValid && mCursor != null) {
    165             mCursor.moveToPosition(position);
    166             return mCursor;
    167         } else {
    168             return null;
    169         }
    170     }
    171 
    172     /**
    173      * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int)
    174      */
    175     @Override
    176     public long getItemId(final int position) {
    177         if (mDataValid && mCursor != null) {
    178             if (mCursor.moveToPosition(position)) {
    179                 return mCursor.getLong(mRowIDColumn);
    180             } else {
    181                 return 0;
    182             }
    183         } else {
    184             return 0;
    185         }
    186     }
    187 
    188     @Override
    189     public VH onCreateViewHolder(final ViewGroup parent, final int viewType) {
    190         return createViewHolder(mContext, parent, viewType);
    191     }
    192 
    193     @Override
    194     public void onBindViewHolder(final VH holder, final int position) {
    195         if (!mDataValid) {
    196             throw new IllegalStateException("this should only be called when the cursor is valid");
    197         }
    198         if (!mCursor.moveToPosition(position)) {
    199             throw new IllegalStateException("couldn't move cursor to position " + position);
    200         }
    201         bindViewHolder(holder, mContext, mCursor);
    202     }
    203     /**
    204      * Bind an existing view to the data pointed to by cursor
    205      * @param view Existing view, returned earlier by newView
    206      * @param context Interface to application's global information
    207      * @param cursor The cursor from which to get the data. The cursor is already
    208      * moved to the correct position.
    209      */
    210     public abstract void bindViewHolder(VH holder, Context context, Cursor cursor);
    211 
    212     /**
    213      * @see android.support.v7.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int)
    214      */
    215     public abstract VH createViewHolder(Context context, ViewGroup parent, int viewType);
    216 
    217     /**
    218      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
    219      * closed.
    220      *
    221      * @param cursor The new cursor to be used
    222      */
    223     public void changeCursor(final Cursor cursor) {
    224         final Cursor old = swapCursor(cursor);
    225         if (old != null) {
    226             old.close();
    227         }
    228     }
    229 
    230     /**
    231      * Swap in a new Cursor, returning the old Cursor.  Unlike
    232      * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
    233      * closed.
    234      *
    235      * @param newCursor The new cursor to be used.
    236      * @return Returns the previously set Cursor, or null if there wasa not one.
    237      * If the given new Cursor is the same instance is the previously set
    238      * Cursor, null is also returned.
    239      */
    240     public Cursor swapCursor(final Cursor newCursor) {
    241         if (newCursor == mCursor) {
    242             return null;
    243         }
    244         final Cursor oldCursor = mCursor;
    245         if (oldCursor != null) {
    246             if (mChangeObserver != null) {
    247                 oldCursor.unregisterContentObserver(mChangeObserver);
    248             }
    249             if (mDataSetObserver != null) {
    250                 oldCursor.unregisterDataSetObserver(mDataSetObserver);
    251             }
    252         }
    253         mCursor = newCursor;
    254         if (newCursor != null) {
    255             if (mChangeObserver != null) {
    256                 newCursor.registerContentObserver(mChangeObserver);
    257             }
    258             if (mDataSetObserver != null) {
    259                 newCursor.registerDataSetObserver(mDataSetObserver);
    260             }
    261             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
    262             mDataValid = true;
    263             // notify the observers about the new cursor
    264             notifyDataSetChanged();
    265         } else {
    266             mRowIDColumn = -1;
    267             mDataValid = false;
    268             // notify the observers about the lack of a data set
    269             notifyDataSetChanged();
    270         }
    271         return oldCursor;
    272     }
    273 
    274     /**
    275      * <p>Converts the cursor into a CharSequence. Subclasses should override this
    276      * method to convert their results. The default implementation returns an
    277      * empty String for null values or the default String representation of
    278      * the value.</p>
    279      *
    280      * @param cursor the cursor to convert to a CharSequence
    281      * @return a CharSequence representing the value
    282      */
    283     public CharSequence convertToString(final Cursor cursor) {
    284         return cursor == null ? "" : cursor.toString();
    285     }
    286 
    287     /**
    288      * Called when the {@link ContentObserver} on the cursor receives a change notification.
    289      * The default implementation provides the auto-requery logic, but may be overridden by
    290      * sub classes.
    291      *
    292      * @see ContentObserver#onChange(boolean)
    293      */
    294     protected void onContentChanged() {
    295         if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
    296             if (false) {
    297                 Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
    298             }
    299             mDataValid = mCursor.requery();
    300         }
    301     }
    302 
    303     private class ChangeObserver extends ContentObserver {
    304         public ChangeObserver() {
    305             super(new Handler());
    306         }
    307 
    308         @Override
    309         public boolean deliverSelfNotifications() {
    310             return true;
    311         }
    312 
    313         @Override
    314         public void onChange(final boolean selfChange) {
    315             onContentChanged();
    316         }
    317     }
    318 
    319     private class MyDataSetObserver extends DataSetObserver {
    320         @Override
    321         public void onChanged() {
    322             mDataValid = true;
    323             notifyDataSetChanged();
    324         }
    325 
    326         @Override
    327         public void onInvalidated() {
    328             mDataValid = false;
    329             notifyDataSetChanged();
    330         }
    331     }
    332 
    333 }
    334