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 
31 import com.android.tv.common.SoftPreconditions;
32 import com.android.tv.data.Channel;
33 import com.android.tv.data.Program;
34 import com.android.tv.dvr.RecordedProgram;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.concurrent.ExecutorService;
39 import java.util.concurrent.Executors;
40 import java.util.concurrent.RejectedExecutionException;
41 
42 /**
43  * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
44  *
45  * <p>Instances of this class should only be executed this using {@link
46  * #executeOnDbThread(Object[])}.
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     private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
58             AsyncDbTask.class.getSimpleName());
59     private static final ExecutorService DB_EXECUTOR = Executors
60             .newSingleThreadExecutor(THREAD_FACTORY);
61 
62     /**
63      * Returns the single tread executor used for DbTasks.
64      */
getExecutor()65     public static ExecutorService getExecutor() {
66         return DB_EXECUTOR;
67     }
68 
69     /**
70      * Executes the given command at some time in the future.
71      *
72      * <p>The command will be executed by {@link #getExecutor()}.
73      *
74      * @param command the runnable task
75      * @throws RejectedExecutionException if this task cannot be
76      *                                    accepted for execution
77      * @throws NullPointerException       if command is null
78      */
execute(Runnable command)79     public static void execute(Runnable command) {
80         DB_EXECUTOR.execute(command);
81     }
82 
83     /**
84      * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
85      * String)}.
86      *
87      * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
88      * which is implemented by subclasses.
89      *
90      * @param <Result> the type of result returned by {@link #onQuery(Cursor)}
91      */
92     public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
93         private final ContentResolver mContentResolver;
94         private final Uri mUri;
95         private final String[] mProjection;
96         private final String mSelection;
97         private final String[] mSelectionArgs;
98         private final String mOrderBy;
99 
100 
AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)101         public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
102                 String selection, String[] selectionArgs, String orderBy) {
103             mContentResolver = contentResolver;
104             mUri = uri;
105             mProjection = projection;
106             mSelection = selection;
107             mSelectionArgs = selectionArgs;
108             mOrderBy = orderBy;
109         }
110 
111         @Override
doInBackground(Void... params)112         protected final Result doInBackground(Void... params) {
113             if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) {
114                 IllegalStateException e = new IllegalStateException(this
115                         + " should only be executed using executeOnDbThread, "
116                         + "but it was called on thread "
117                         + Thread.currentThread());
118                 Log.w(TAG, e);
119                 if (DEBUG) {
120                     throw e;
121                 }
122             }
123 
124             if (isCancelled()) {
125                 // This is guaranteed to never call onPostExecute because the task is canceled.
126                 return null;
127             }
128             if (DEBUG) {
129                 Log.v(TAG, "Starting query for " + this);
130             }
131             try (Cursor c = mContentResolver
132                     .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
133                 if (c != null && !isCancelled()) {
134                     Result result = onQuery(c);
135                     if (DEBUG) {
136                         Log.v(TAG, "Finished query for " + this);
137                     }
138                     return result;
139                 } else {
140                     if (c == null) {
141                         Log.e(TAG, "Unknown query error for " + this);
142                     } else {
143                         if (DEBUG) {
144                             Log.d(TAG, "Canceled query for " + this);
145                         }
146                     }
147                     return null;
148                 }
149             } catch (Exception e) {
150                 SoftPreconditions.warn(TAG, null, "Error querying " + this, e);
151                 return null;
152             }
153         }
154 
155         /**
156          * Return the result from the cursor.
157          *
158          * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
159          */
160         @WorkerThread
onQuery(Cursor c)161         protected abstract Result onQuery(Cursor c);
162 
163         @Override
toString()164         public String toString() {
165             return this.getClass().getName() + "(" + mUri + ")";
166         }
167     }
168 
169     /**
170      * Returns the result of a query as an {@link List} of {@code T}.
171      *
172      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
173      *
174      * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)}
175      */
176     public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
177         private final CursorFilter mFilter;
178 
AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)179         public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
180                 String selection, String[] selectionArgs, String orderBy) {
181             this(contentResolver, uri, projection, selection, selectionArgs, orderBy, null);
182         }
183 
AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy, CursorFilter filter)184         public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
185                 String selection, String[] selectionArgs, String orderBy, CursorFilter filter) {
186             super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
187             mFilter = filter;
188         }
189 
190         @Override
onQuery(Cursor c)191         protected final List<T> onQuery(Cursor c) {
192             List<T> result = new ArrayList<>();
193             while (c.moveToNext()) {
194                 if (isCancelled()) {
195                     // This is guaranteed to never call onPostExecute because the task is canceled.
196                     return null;
197                 }
198                 if (mFilter != null && !mFilter.filter(c)) {
199                     continue;
200                 }
201                 T t = fromCursor(c);
202                 result.add(t);
203             }
204             if (DEBUG) {
205                 Log.v(TAG, "Found " + result.size() + " for  " + this);
206             }
207             return result;
208         }
209 
210         /**
211          * Return a single instance of {@code T} from the cursor.
212          *
213          * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
214          * #onQuery(Cursor)}.
215          *
216          * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
217          *
218          * @param c The cursor with the values to create T from.
219          */
220         @WorkerThread
fromCursor(Cursor c)221         protected abstract T fromCursor(Cursor c);
222     }
223 
224     /**
225      * Returns the result of a query as a single instance of {@code T}.
226      *
227      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
228      */
229     public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> {
230 
AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)231         public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection,
232                 String selection, String[] selectionArgs, String orderBy) {
233             super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
234         }
235 
236         @Override
onQuery(Cursor c)237         protected final T onQuery(Cursor c) {
238             if (c.moveToNext()) {
239                 if (isCancelled()) {
240                     // This is guaranteed to never call onPostExecute because the task is canceled.
241                     return null;
242                 }
243                 T result = fromCursor(c);
244                 if (c.moveToNext()) {
245                     Log.w(TAG, "More than one result for found for  " + this);
246                 }
247                 return result;
248             } else {
249                 if (DEBUG) {
250                     Log.v(TAG, "No result for found  for  " + this);
251                 }
252                 return null;
253             }
254 
255         }
256 
257         /**
258          * Return a single instance of {@code T} from the cursor.
259          *
260          * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
261          * #onQuery(Cursor)}.
262          *
263          * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
264          *
265          * @param c The cursor with the values to create T from.
266          */
267         @WorkerThread
fromCursor(Cursor c)268         protected abstract T fromCursor(Cursor c);
269     }
270 
271     /**
272      * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
273      */
274     public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
275 
AsyncChannelQueryTask(ContentResolver contentResolver)276         public AsyncChannelQueryTask(ContentResolver contentResolver) {
277             super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
278                     null, null, null);
279         }
280 
281         @Override
fromCursor(Cursor c)282         protected final Channel fromCursor(Cursor c) {
283             return Channel.fromCursor(c);
284         }
285     }
286 
287     /**
288      * Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}.
289      */
290     public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> {
AsyncProgramQueryTask(ContentResolver contentResolver)291         public AsyncProgramQueryTask(ContentResolver contentResolver) {
292             super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null);
293         }
294 
AsyncProgramQueryTask(ContentResolver contentResolver, Uri uri, String selection, String[] selectionArgs, String sortOrder, CursorFilter filter)295         public AsyncProgramQueryTask(ContentResolver contentResolver, Uri uri, String selection,
296                 String[] selectionArgs, String sortOrder, CursorFilter filter) {
297             super(contentResolver, uri, Program.PROJECTION, selection, selectionArgs, sortOrder,
298                     filter);
299         }
300 
301         @Override
fromCursor(Cursor c)302         protected final Program fromCursor(Cursor c) {
303             return Program.fromCursor(c);
304         }
305     }
306 
307     /**
308      * Gets an {@link List} of {@link TvContract.RecordedPrograms}s.
309      */
310     public abstract static class AsyncRecordedProgramQueryTask
311             extends AsyncQueryListTask<RecordedProgram> {
AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri)312         public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) {
313             super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
314         }
315 
316         @Override
fromCursor(Cursor c)317         protected final RecordedProgram fromCursor(Cursor c) {
318             return RecordedProgram.fromCursor(c);
319         }
320     }
321 
322     /**
323      * Execute the task on the {@link #DB_EXECUTOR} thread.
324      */
325     @SafeVarargs
326     @MainThread
executeOnDbThread(Params... params)327     public final void executeOnDbThread(Params... params) {
328         executeOnExecutor(DB_EXECUTOR, params);
329     }
330 
331     /**
332      * Gets an {@link List} of {@link Program}s for a given channel and period {@link
333      * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is
334      * {@code null}, then all the programs is queried.
335      */
336     public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask {
337         protected final Range<Long> mPeriod;
338         protected final long mChannelId;
339 
LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, @Nullable Range<Long> period)340         public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
341                 @Nullable Range<Long> period) {
342             super(contentResolver, period == null
343                     ? TvContract.buildProgramsUriForChannel(channelId)
344                     : TvContract.buildProgramsUriForChannel(channelId, period.getLower(),
345                             period.getUpper()),
346                     null, null, null, null);
347             mPeriod = period;
348             mChannelId = channelId;
349         }
350 
getChannelId()351         public long getChannelId() {
352             return mChannelId;
353         }
354 
getPeriod()355         public final Range<Long> getPeriod() {
356             return mPeriod;
357         }
358     }
359 
360     /**
361      * Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}.
362      */
363     public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> {
364 
AsyncQueryProgramTask(ContentResolver contentResolver, long programId)365         public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) {
366             super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null,
367                     null, null);
368         }
369 
370         @Override
fromCursor(Cursor c)371         protected Program fromCursor(Cursor c) {
372             return Program.fromCursor(c);
373         }
374     }
375 
376     /**
377      * An interface which filters the row.
378      */
379     public interface CursorFilter extends Filter<Cursor> { }
380 }
381