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.net.Uri; 23 import android.os.AsyncTask; 24 import android.support.annotation.MainThread; 25 import android.support.annotation.Nullable; 26 import android.support.annotation.WorkerThread; 27 import android.util.Log; 28 import android.util.Range; 29 30 import com.android.tv.common.SoftPreconditions; 31 import com.android.tv.data.Channel; 32 import com.android.tv.data.Program; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.ExecutorService; 37 import java.util.concurrent.Executors; 38 import java.util.concurrent.RejectedExecutionException; 39 40 /** 41 * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. 42 * 43 * <p>Instances of this class should only be executed this using {@link 44 * #executeOnDbThread(Object[])}. 45 * 46 * @param <Params> the type of the parameters sent to the task upon execution. 47 * @param <Progress> the type of the progress units published during the background computation. 48 * @param <Result> the type of the result of the background computation. 49 */ 50 public abstract class AsyncDbTask<Params, Progress, Result> 51 extends AsyncTask<Params, Progress, Result> { 52 private static final String TAG = "AsyncDbTask"; 53 private static final boolean DEBUG = false; 54 55 public static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory( 56 AsyncDbTask.class.getSimpleName()); 57 private static final ExecutorService DB_EXECUTOR = Executors 58 .newSingleThreadExecutor(THREAD_FACTORY); 59 60 /** 61 * Returns the single tread executor used for DbTasks. 62 */ getExecutor()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 74 * accepted for execution 75 * @throws NullPointerException if command is null 76 */ execute(Runnable command)77 public static void execute(Runnable command) { 78 DB_EXECUTOR.execute(command); 79 } 80 81 /** 82 * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[], 83 * String)}. 84 * 85 * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} 86 * which is implemented by subclasses. 87 * 88 * @param <Result> the type of result returned by {@link #onQuery(Cursor)} 89 */ 90 public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> { 91 private final ContentResolver mContentResolver; 92 private final Uri mUri; 93 private final String[] mProjection; 94 private final String mSelection; 95 private final String[] mSelectionArgs; 96 private final String mOrderBy; 97 98 AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)99 public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, 100 String selection, String[] selectionArgs, String orderBy) { 101 mContentResolver = contentResolver; 102 mUri = uri; 103 mProjection = projection; 104 mSelection = selection; 105 mSelectionArgs = selectionArgs; 106 mOrderBy = orderBy; 107 } 108 109 @Override doInBackground(Void... params)110 protected final Result doInBackground(Void... params) { 111 if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) { 112 IllegalStateException e = new IllegalStateException(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 (DEBUG) { 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 if (DEBUG) { 127 Log.v(TAG, "Starting query for " + this); 128 } 129 try (Cursor c = mContentResolver 130 .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) { 131 if (c != null && !isCancelled()) { 132 Result result = onQuery(c); 133 if (DEBUG) { 134 Log.v(TAG, "Finished query for " + this); 135 } 136 return result; 137 } else { 138 if (c == null) { 139 Log.e(TAG, "Unknown query error for " + this); 140 } else { 141 if (DEBUG) { 142 Log.d(TAG, "Canceled query for " + this); 143 } 144 } 145 return null; 146 } 147 } catch (Exception e) { 148 SoftPreconditions.warn(TAG, null, "Error querying " + this, e); 149 return null; 150 } 151 } 152 153 /** 154 * Return the result from the cursor. 155 * 156 * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)} 157 */ 158 @WorkerThread onQuery(Cursor c)159 protected abstract Result onQuery(Cursor c); 160 161 @Override toString()162 public String toString() { 163 return this.getClass().getSimpleName() + "(" + mUri + ")"; 164 } 165 } 166 167 /** 168 * Returns the result of a query as an {@link List} of {@code T}. 169 * 170 * <p>Subclasses must implement {@link #fromCursor(Cursor)}. 171 * 172 * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)} 173 */ 174 public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> { 175 AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)176 public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, 177 String selection, String[] selectionArgs, String orderBy) { 178 super(contentResolver, uri, projection, selection, selectionArgs, orderBy); 179 } 180 181 @Override onQuery(Cursor c)182 protected final List<T> onQuery(Cursor c) { 183 List<T> result = new ArrayList<>(); 184 while (c.moveToNext()) { 185 if (isCancelled()) { 186 // This is guaranteed to never call onPostExecute because the task is canceled. 187 return null; 188 } 189 T t = fromCursor(c); 190 result.add(t); 191 } 192 if (DEBUG) { 193 Log.v(TAG, "Found " + result.size() + " for " + this); 194 } 195 return result; 196 } 197 198 /** 199 * Return a single instance of {@code T} from the cursor. 200 * 201 * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link 202 * #onQuery(Cursor)}. 203 * 204 * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)} 205 * 206 * @param c The cursor with the values to create T from. 207 */ 208 @WorkerThread fromCursor(Cursor c)209 protected abstract T fromCursor(Cursor c); 210 } 211 212 /** 213 * Returns the result of a query as a single instance of {@code T}. 214 * 215 * <p>Subclasses must implement {@link #fromCursor(Cursor)}. 216 */ 217 public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> { 218 AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)219 public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, 220 String selection, String[] selectionArgs, String orderBy) { 221 super(contentResolver, uri, projection, selection, selectionArgs, orderBy); 222 } 223 224 @Override onQuery(Cursor c)225 protected final T onQuery(Cursor c) { 226 if (c.moveToNext()) { 227 if (isCancelled()) { 228 // This is guaranteed to never call onPostExecute because the task is canceled. 229 return null; 230 } 231 T result = fromCursor(c); 232 if (c.moveToNext()) { 233 Log.w(TAG, "More than one result for found for " + this); 234 } 235 return result; 236 } else { 237 if (DEBUG) { 238 Log.v(TAG, "No result for found for " + this); 239 } 240 return null; 241 } 242 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 * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. 261 */ 262 public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> { 263 AsyncChannelQueryTask(ContentResolver contentResolver)264 public AsyncChannelQueryTask(ContentResolver contentResolver) { 265 super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION, 266 null, null, null); 267 } 268 269 @Override fromCursor(Cursor c)270 protected final Channel fromCursor(Cursor c) { 271 return Channel.fromCursor(c); 272 } 273 } 274 275 /** 276 * Execute the task on the {@link #DB_EXECUTOR} thread. 277 */ 278 @SafeVarargs 279 @MainThread executeOnDbThread(Params... params)280 public final void executeOnDbThread(Params... params) { 281 executeOnExecutor(DB_EXECUTOR, params); 282 } 283 284 /** 285 * Gets an {@link List} of {@link Program}s for a given channel and period {@link 286 * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is 287 * {@code null}, then all the programs is queried. 288 */ 289 public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> { 290 protected final Range<Long> mPeriod; 291 protected final long mChannelId; 292 LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, @Nullable Range<Long> period)293 public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, 294 @Nullable Range<Long> period) { 295 super(contentResolver, period == null 296 ? TvContract.buildProgramsUriForChannel(channelId) 297 : TvContract.buildProgramsUriForChannel(channelId, period.getLower(), 298 period.getUpper()), 299 Program.PROJECTION, null, null, null); 300 mPeriod = period; 301 mChannelId = channelId; 302 } 303 304 @Override fromCursor(Cursor c)305 protected final Program fromCursor(Cursor c) { 306 return Program.fromCursor(c); 307 } 308 getChannelId()309 public long getChannelId() { 310 return mChannelId; 311 } 312 getPeriod()313 public final Range<Long> getPeriod() { 314 return mPeriod; 315 } 316 } 317 } 318