AsyncDbTask.java revision 4a5144ac8c51c4d89d1359e13e37fcd7f928ed9a
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 17package com.android.tv.util; 18 19import android.content.ContentResolver; 20import android.database.Cursor; 21import android.media.tv.TvContract; 22import android.media.tv.TvContract.Programs; 23import android.net.Uri; 24import android.os.AsyncTask; 25import android.support.annotation.MainThread; 26import android.support.annotation.Nullable; 27import android.support.annotation.WorkerThread; 28import android.util.Log; 29import android.util.Range; 30import com.android.tv.common.SoftPreconditions; 31import com.android.tv.common.concurrent.NamedThreadFactory; 32import com.android.tv.data.Channel; 33import com.android.tv.data.Program; 34import com.android.tv.dvr.data.RecordedProgram; 35import java.util.ArrayList; 36import java.util.List; 37import java.util.concurrent.ExecutorService; 38import java.util.concurrent.Executors; 39import java.util.concurrent.RejectedExecutionException; 40 41/** 42 * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. 43 * 44 * <p>Instances of this class should only be executed this using {@link 45 * #executeOnDbThread(Object[])}. 46 * 47 * @paramthe type of the parameters sent to the task upon execution. 48 * @param the type of the progress units published during the background computation. 49 * @param the type of the result of the background computation. 50 */ 51@SuppressWarnings("TryWithResources") // TODO(b/62143348): remove when error prone check fixed 52public abstract class AsyncDbTask<Params, Progress, Result> 53 extends AsyncTask<Params, Progress, Result> { 54 private static final String TAG = "AsyncDbTask"; 55 private static final boolean DEBUG = false; 56 57 private static final NamedThreadFactory THREAD_FACTORY = 58 new NamedThreadFactory(AsyncDbTask.class.getSimpleName()); 59 private static final ExecutorService DB_EXECUTOR = 60 Executors.newSingleThreadExecutor(THREAD_FACTORY); 61 62 /** Returns the single tread executor used for DbTasks. */ 63 public static ExecutorService getExecutor() { 64 return DB_EXECUTOR; 65 } 66 67 /** 68 * Executes the given command at some time in the future. 69 * 70 * <p>The command will be executed by {@link #getExecutor()}. 71 * 72 * @param command the runnable task 73 * @throws RejectedExecutionException if this task cannot be accepted for execution 74 * @throws NullPointerException if command is null 75 */ 76 public static void executeOnDbThread(Runnable command) { 77 DB_EXECUTOR.execute(command); 78 } 79 80 /** 81 * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[], 82 * String)}. 83 * 84 * <p>{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} which 85 * is implemented by subclasses. 86 * 87 * @param the type of result returned by {@link #onQuery(Cursor)} 88 */ 89 public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> { 90 private final ContentResolver mContentResolver; 91 private final Uri mUri; 92 private final String[] mProjection; 93 private final String mSelection; 94 private final String[] mSelectionArgs; 95 private final String mOrderBy; 96 97 public AsyncQueryTask( 98 ContentResolver contentResolver, 99 Uri uri, 100 String[] projection, 101 String selection, 102 String[] selectionArgs, 103 String orderBy) { 104 mContentResolver = contentResolver; 105 mUri = uri; 106 mProjection = projection; 107 mSelection = selection; 108 mSelectionArgs = selectionArgs; 109 mOrderBy = orderBy; 110 } 111 112 @Override 113 protected final Result doInBackground(Void... params) { 114 if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) { 115 IllegalStateException e = 116 new IllegalStateException( 117 this 118 + " should only be executed using executeOnDbThread, " 119 + "but it was called on thread " 120 + Thread.currentThread()); 121 Log.w(TAG, e); 122 if (DEBUG) { 123 throw e; 124 } 125 } 126 127 if (isCancelled()) { 128 // This is guaranteed to never call onPostExecute because the task is canceled. 129 return null; 130 } 131 if (DEBUG) { 132 Log.v(TAG, "Starting query for " + this); 133 } 134 try (Cursor c = 135 mContentResolver.query( 136 mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) { 137 if (c != null && !isCancelled()) { 138 Result result = onQuery(c); 139 if (DEBUG) { 140 Log.v(TAG, "Finished query for " + this); 141 } 142 return result; 143 } else { 144 if (c == null) { 145 Log.e(TAG, "Unknown query error for " + this); 146 } else { 147 if (DEBUG) { 148 Log.d(TAG, "Canceled query for " + this); 149 } 150 } 151 return null; 152 } 153 } catch (Exception e) { 154 SoftPreconditions.warn(TAG, null, e, "Error querying " + this); 155 return null; 156 } 157 } 158 159 /** 160 * Return the result from the cursor. 161 * 162 * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)} 163 */ 164 @WorkerThread 165 protected abstract Result onQuery(Cursor c); 166 167 @Override 168 public String toString() { 169 return this.getClass().getName() + "(" + mUri + ")"; 170 } 171 } 172 173 /** 174 * Returns the result of a query as an {@link List} of {@code T}. 175 * 176 * <p>Subclasses must implement {@link #fromCursor(Cursor)}. 177 * 178 * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)} 179 */ 180 public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> { 181 private final CursorFilter mFilter; 182 183 public AsyncQueryListTask( 184 ContentResolver contentResolver, 185 Uri uri, 186 String[] projection, 187 String selection, 188 String[] selectionArgs, 189 String orderBy) { 190 this(contentResolver, uri, projection, selection, selectionArgs, orderBy, null); 191 } 192 193 public AsyncQueryListTask( 194 ContentResolver contentResolver, 195 Uri uri, 196 String[] projection, 197 String selection, 198 String[] selectionArgs, 199 String orderBy, 200 CursorFilter filter) { 201 super(contentResolver, uri, projection, selection, selectionArgs, orderBy); 202 mFilter = filter; 203 } 204 205 @Override 206 protected final List<T> onQuery(Cursor c) { 207 List<T> result = new ArrayList<>(); 208 while (c.moveToNext()) { 209 if (isCancelled()) { 210 // This is guaranteed to never call onPostExecute because the task is canceled. 211 return null; 212 } 213 if (mFilter != null && !mFilter.filter(c)) { 214 continue; 215 } 216 T t = fromCursor(c); 217 result.add(t); 218 } 219 if (DEBUG) { 220 Log.v(TAG, "Found " + result.size() + " for " + this); 221 } 222 return result; 223 } 224 225 /** 226 * Return a single instance of {@code T} from the cursor. 227 * 228 * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link 229 * #onQuery(Cursor)}. 230 * 231 * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)} 232 * 233 * @param c The cursor with the values to create T from. 234 */ 235 @WorkerThread 236 protected abstract T fromCursor(Cursor c); 237 } 238 239 /** 240 * Returns the result of a query as a single instance of {@code T}. 241 * 242 * <p>Subclasses must implement {@link #fromCursor(Cursor)}. 243 */ 244 public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> { 245 246 public AsyncQueryItemTask( 247 ContentResolver contentResolver, 248 Uri uri, 249 String[] projection, 250 String selection, 251 String[] selectionArgs, 252 String orderBy) { 253 super(contentResolver, uri, projection, selection, selectionArgs, orderBy); 254 } 255 256 @Override 257 protected final T onQuery(Cursor c) { 258 if (c.moveToNext()) { 259 if (isCancelled()) { 260 // This is guaranteed to never call onPostExecute because the task is canceled. 261 return null; 262 } 263 T result = fromCursor(c); 264 if (c.moveToNext()) { 265 Log.w(TAG, "More than one result for found for " + this); 266 } 267 return result; 268 } else { 269 if (DEBUG) { 270 Log.v(TAG, "No result for found for " + this); 271 } 272 return null; 273 } 274 } 275 276 /** 277 * Return a single instance of {@code T} from the cursor. 278 * 279 * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link 280 * #onQuery(Cursor)}. 281 * 282 * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)} 283 * 284 * @param c The cursor with the values to create T from. 285 */ 286 @WorkerThread 287 protected abstract T fromCursor(Cursor c); 288 } 289 290 /** Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. */ 291 public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> { 292 293 public AsyncChannelQueryTask(ContentResolver contentResolver) { 294 super( 295 contentResolver, 296 TvContract.Channels.CONTENT_URI, 297 Channel.PROJECTION, 298 null, 299 null, 300 null); 301 } 302 303 @Override 304 protected final Channel fromCursor(Cursor c) { 305 return Channel.fromCursor(c); 306 } 307 } 308 309 /** Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. */ 310 public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> { 311 public AsyncProgramQueryTask(ContentResolver contentResolver) { 312 super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null); 313 } 314 315 public AsyncProgramQueryTask( 316 ContentResolver contentResolver, 317 Uri uri, 318 String selection, 319 String[] selectionArgs, 320 String sortOrder, 321 CursorFilter filter) { 322 super( 323 contentResolver, 324 uri, 325 Program.PROJECTION, 326 selection, 327 selectionArgs, 328 sortOrder, 329 filter); 330 } 331 332 @Override 333 protected final Program fromCursor(Cursor c) { 334 return Program.fromCursor(c); 335 } 336 } 337 338 /** Gets an {@link List} of {@link TvContract.RecordedPrograms}s. */ 339 public abstract static class AsyncRecordedProgramQueryTask 340 extends AsyncQueryListTask<RecordedProgram> { 341 public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) { 342 super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null); 343 } 344 345 @Override 346 protected final RecordedProgram fromCursor(Cursor c) { 347 return RecordedProgram.fromCursor(c); 348 } 349 } 350 351 /** Execute the task on the {@link #DB_EXECUTOR} thread. */ 352 @SafeVarargs 353 @MainThread 354 public final void executeOnDbThread(Params... params) { 355 executeOnExecutor(DB_EXECUTOR, params); 356 } 357 358 /** 359 * Gets an {@link List} of {@link Program}s for a given channel and period {@link 360 * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is {@code 361 * null}, then all the programs is queried. 362 */ 363 public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask { 364 protected final Range<Long> mPeriod; 365 protected final long mChannelId; 366 367 public LoadProgramsForChannelTask( 368 ContentResolver contentResolver, long channelId, @Nullable Range<Long> period) { 369 super( 370 contentResolver, 371 period == null 372 ? TvContract.buildProgramsUriForChannel(channelId) 373 : TvContract.buildProgramsUriForChannel( 374 channelId, period.getLower(), period.getUpper()), 375 null, 376 null, 377 null, 378 null); 379 mPeriod = period; 380 mChannelId = channelId; 381 } 382 383 public long getChannelId() { 384 return mChannelId; 385 } 386 387 public final Range<Long> getPeriod() { 388 return mPeriod; 389 } 390 } 391 392 /** Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. */ 393 public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> { 394 395 public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) { 396 super( 397 contentResolver, 398 TvContract.buildProgramUri(programId), 399 Program.PROJECTION, 400 null, 401 null, 402 null); 403 } 404 405 @Override 406 protected Program fromCursor(Cursor c) { 407 return Program.fromCursor(c); 408 } 409 } 410 411 /** An interface which filters the row. */ 412 public interface CursorFilter extends Filter<Cursor> {} 413} 414