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