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