• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * @param <Params> the type of the parameters sent to the task upon execution.
45  * @param <Progress> the type of the progress units published during the background computation.
46  * @param <Result> 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 
AsyncDbTask(Executor mExecutor)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 <Result> 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 
AsyncQueryTask( Executor executor, ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)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
doInBackground(Void... params)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
onQuery(Cursor c)147         protected abstract Result onQuery(Cursor c);
148 
149         @Override
toString()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 
AsyncQueryListTask( Executor executor, ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)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 
AsyncQueryListTask( Executor executor, ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy, CursorFilter filter)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
onQuery(Cursor c)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
fromCursor(Cursor c)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 
AsyncQueryItemTask( Executor executor, ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)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
onQuery(Cursor c)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
fromCursor(Cursor c)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 
AsyncChannelQueryTask(Executor executor, ContentResolver contentResolver)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
fromCursor(Cursor c)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> {
AsyncProgramQueryTask(Executor executor, ContentResolver contentResolver)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 
AsyncProgramQueryTask( Executor executor, ContentResolver contentResolver, Uri uri, String selection, String[] selectionArgs, String sortOrder, CursorFilter filter)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
fromCursor(Cursor c)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> {
AsyncRecordedProgramQueryTask( Executor executor, ContentResolver contentResolver, Uri uri)344         public AsyncRecordedProgramQueryTask(
345                 Executor executor, ContentResolver contentResolver, Uri uri) {
346             super(executor, contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
347         }
348 
349         @Override
fromCursor(Cursor c)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
executeOnDbThread(Params... params)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 
LoadProgramsForChannelTask( Executor executor, ContentResolver contentResolver, long channelId, @Nullable Range<Long> period)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 
getChannelId()392         public long getChannelId() {
393             return mChannelId;
394         }
395 
getPeriod()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 
AsyncQueryProgramTask( Executor executor, ContentResolver contentResolver, long programId)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
fromCursor(Cursor c)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