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