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