AsyncDbTask.java revision 3a72b93e554bd22a5c64e71a6956d9604ce05108
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.net.Uri; 23import android.os.AsyncTask; 24import android.support.annotation.MainThread; 25import android.support.annotation.WorkerThread; 26import android.util.Log; 27import android.util.Range; 28 29import com.android.tv.data.Channel; 30import com.android.tv.data.Program; 31 32import java.util.ArrayList; 33import java.util.List; 34import java.util.concurrent.ExecutorService; 35import java.util.concurrent.Executors; 36import java.util.concurrent.RejectedExecutionException; 37 38/** 39 * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. 40 * 41 * <p>Instances of this class should only be executed this using {@link 42 * #executeOnDbThread(Object[])}. 43 */ 44public abstract class AsyncDbTask<Params, Progress, Result> 45 extends AsyncTask<Params, Progress, Result> { 46 private static final String TAG = "AsyncDbTask"; 47 private static final boolean DEBUG = false; 48 49 public static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory( 50 AsyncDbTask.class.getSimpleName()); 51 private static final ExecutorService DB_EXECUTOR = Executors 52 .newSingleThreadExecutor(THREAD_FACTORY); 53 54 /** 55 * Returns the single tread executor used for DbTasks. 56 */ 57 public static ExecutorService getExecutor() { 58 return DB_EXECUTOR; 59 } 60 61 /** 62 * Executes the given command at some time in the future. 63 * 64 * <p>The command will be executed by {@link #getExecutor()}. 65 * 66 * @param command the runnable task 67 * @throws RejectedExecutionException if this task cannot be 68 * accepted for execution 69 * @throws NullPointerException if command is null 70 */ 71 public static void execute(Runnable command) { 72 DB_EXECUTOR.execute(command); 73 } 74 75 /** 76 * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[], 77 * String)}. 78 * 79 * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} 80 * which is implemented by subclasses. 81 * 82 * @paramThe type of result returned by {@link #onQuery(Cursor)} 83 */ 84 public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> { 85 private final ContentResolver mContentResolver; 86 private final Uri mUri; 87 private final String[] mProjection; 88 private final String mSelection; 89 private final String[] mSelectionArgs; 90 private final String mOrderBy; 91 92 93 public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, 94 String selection, String[] selectionArgs, String orderBy) { 95 mContentResolver = contentResolver; 96 mUri = uri; 97 mProjection = projection; 98 mSelection = selection; 99 mSelectionArgs = selectionArgs; 100 mOrderBy = orderBy; 101 } 102 103 @Override 104 protected final Result doInBackground(Void... params) { 105 if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) { 106 IllegalStateException e = new IllegalStateException( 107 this + " should only be executed using executeOnDbThread, " + 108 "but it was called on thread " + Thread.currentThread()); 109 Log.w(TAG, e); 110 if (DEBUG) { 111 throw e; 112 } 113 } 114 115 if (isCancelled()) { 116 // This is guaranteed to never call onPostExecute because the task is canceled. 117 return null; 118 } 119 if (DEBUG) { 120 Log.v(TAG, "Starting query for " + this); 121 } 122 try (Cursor c = mContentResolver 123 .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) { 124 if (c != null && !isCancelled()) { 125 Result result = onQuery(c); 126 if (DEBUG) { 127 Log.v(TAG, "Finished query for " + this); 128 } 129 return result; 130 } else { 131 if (c == null) { 132 Log.e(TAG, "Unknown query error for " + this); 133 } else { 134 if (DEBUG) { 135 Log.d(TAG, "Canceled query for " + this); 136 } 137 } 138 return null; 139 } 140 } catch (SecurityException e) { 141 Log.d(TAG, "Security exception during query", e); 142 return null; 143 } 144 } 145 146 /** 147 * Return the result from the cursor. 148 * 149 * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)} 150 */ 151 @WorkerThread 152 protected abstract Result onQuery(Cursor c); 153 154 @Override 155 public String toString() { 156 return this.getClass().getSimpleName() + "(" + mUri + ")"; 157 } 158 } 159 160 /** 161 * Returns the result of a query as an {@link List} of {@code T}. 162 * 163 * <p>Subclasses must implement {@link #fromCursor(Cursor)}. 164 */ 165 public static abstract class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> { 166 167 public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, 168 String selection, String[] selectionArgs, String orderBy) { 169 super(contentResolver, uri, projection, selection, selectionArgs, orderBy); 170 } 171 172 @Override 173 protected final List<T> onQuery(Cursor c) { 174 List<T> result = new ArrayList<>(); 175 while (c.moveToNext()) { 176 if (isCancelled()) { 177 // This is guaranteed to never call onPostExecute because the task is canceled. 178 return null; 179 } 180 T t = fromCursor(c); 181 result.add(t); 182 } 183 if (DEBUG) { 184 Log.v(TAG, "Found " + result.size() + " for " + this); 185 } 186 return result; 187 } 188 189 /** 190 * Return a single instance of {@code T} from the cursor. 191 * 192 * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link 193 * #onQuery(Cursor)}. 194 * 195 * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)} 196 * 197 * @param c The cursor with the values to create T from. 198 */ 199 @WorkerThread 200 protected abstract T fromCursor(Cursor c); 201 } 202 203 /** 204 * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. 205 */ 206 public static abstract class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> { 207 208 public AsyncChannelQueryTask(ContentResolver contentResolver) { 209 super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION, 210 null, null, null); 211 } 212 213 @Override 214 protected final Channel fromCursor(Cursor c) { 215 return Channel.fromCursor(c); 216 } 217 } 218 219 /** 220 * Execute the task on the {@link #DB_EXECUTOR} thread. 221 */ 222 @SafeVarargs 223 @MainThread 224 public final void executeOnDbThread(Params... params) { 225 executeOnExecutor(DB_EXECUTOR, params); 226 } 227 228 /** 229 * Gets an {@link List} of {@link Program}s for a given channel and period {@link 230 * TvContract#buildProgramsUriForChannel(long, long, long)}. 231 */ 232 public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> { 233 protected final Range<Long> mPeriod; 234 protected final long mChannelId; 235 236 public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, 237 Range<Long> period) { 238 super(contentResolver, TvContract 239 .buildProgramsUriForChannel(channelId, period.getLower(), period.getUpper()), 240 Program.PROJECTION, null, null, null); 241 mPeriod = period; 242 mChannelId = channelId; 243 } 244 245 @Override 246 protected final Program fromCursor(Cursor c) { 247 return Program.fromCursor(c); 248 } 249 250 public long getChannelId() { 251 return mChannelId; 252 } 253 254 public final Range<Long> getPeriod() { 255 return mPeriod; 256 } 257 } 258} 259