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 androidx.recyclerview.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 */ CursorRecyclerAdapter(final Context context, final Cursor c, final int flags)109 public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) { 110 init(context, c, flags); 111 } 112 init(final Context context, final Cursor c, int flags)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 */ getCursor()147 public Cursor getCursor() { 148 return mCursor; 149 } 150 151 @Override getItemCount()152 public int getItemCount() { 153 if (mDataValid && mCursor != null) { 154 return mCursor.getCount(); 155 } else { 156 return 0; 157 } 158 } 159 160 /** 161 * @see androidx.recyclerview.widget.RecyclerView.Adapter#getItem(int) 162 */ getItem(final int position)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 androidx.recyclerview.widget.RecyclerView.Adapter#getItemId(int) 174 */ 175 @Override getItemId(final int position)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 onCreateViewHolder(final ViewGroup parent, final int viewType)189 public VH onCreateViewHolder(final ViewGroup parent, final int viewType) { 190 return createViewHolder(mContext, parent, viewType); 191 } 192 193 @Override onBindViewHolder(final VH holder, final int position)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 */ bindViewHolder(VH holder, Context context, Cursor cursor)210 public abstract void bindViewHolder(VH holder, Context context, Cursor cursor); 211 212 /** 213 * @see androidx.recyclerview.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int) 214 */ createViewHolder(Context context, ViewGroup parent, int viewType)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 */ changeCursor(final Cursor cursor)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 */ swapCursor(final Cursor newCursor)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 */ convertToString(final Cursor cursor)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 */ onContentChanged()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 { ChangeObserver()304 public ChangeObserver() { 305 super(new Handler()); 306 } 307 308 @Override deliverSelfNotifications()309 public boolean deliverSelfNotifications() { 310 return true; 311 } 312 313 @Override onChange(final boolean selfChange)314 public void onChange(final boolean selfChange) { 315 onContentChanged(); 316 } 317 } 318 319 private class MyDataSetObserver extends DataSetObserver { 320 @Override onChanged()321 public void onChanged() { 322 mDataValid = true; 323 notifyDataSetChanged(); 324 } 325 326 @Override onInvalidated()327 public void onInvalidated() { 328 mDataValid = false; 329 notifyDataSetChanged(); 330 } 331 } 332 333 } 334