• 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.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