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