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.net.Uri;
23 import android.os.AsyncTask;
24 import android.support.annotation.MainThread;
25 import android.support.annotation.Nullable;
26 import android.support.annotation.WorkerThread;
27 import android.util.Log;
28 import android.util.Range;
29 
30 import com.android.tv.common.SoftPreconditions;
31 import com.android.tv.data.Channel;
32 import com.android.tv.data.Program;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.RejectedExecutionException;
39 
40 /**
41  * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
42  *
43  * <p>Instances of this class should only be executed this using {@link
44  * #executeOnDbThread(Object[])}.
45  *
46  * @param <Params> the type of the parameters sent to the task upon execution.
47  * @param <Progress> the type of the progress units published during the background computation.
48  * @param <Result> the type of the result of the background computation.
49  */
50 public abstract class AsyncDbTask<Params, Progress, Result>
51         extends AsyncTask<Params, Progress, Result> {
52     private static final String TAG = "AsyncDbTask";
53     private static final boolean DEBUG = false;
54 
55     public static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
56             AsyncDbTask.class.getSimpleName());
57     private static final ExecutorService DB_EXECUTOR = Executors
58             .newSingleThreadExecutor(THREAD_FACTORY);
59 
60     /**
61      * Returns the single tread executor used for DbTasks.
62      */
getExecutor()63     public static ExecutorService getExecutor() {
64         return DB_EXECUTOR;
65     }
66 
67     /**
68      * Executes the given command at some time in the future.
69      *
70      * <p>The command will be executed by {@link #getExecutor()}.
71      *
72      * @param command the runnable task
73      * @throws RejectedExecutionException if this task cannot be
74      *                                    accepted for execution
75      * @throws NullPointerException       if command is null
76      */
execute(Runnable command)77     public static void execute(Runnable command) {
78         DB_EXECUTOR.execute(command);
79     }
80 
81     /**
82      * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
83      * String)}.
84      *
85      * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
86      * which is implemented by subclasses.
87      *
88      * @param <Result> the type of result returned by {@link #onQuery(Cursor)}
89      */
90     public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
91         private final ContentResolver mContentResolver;
92         private final Uri mUri;
93         private final String[] mProjection;
94         private final String mSelection;
95         private final String[] mSelectionArgs;
96         private final String mOrderBy;
97 
98 
AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)99         public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
100                 String selection, String[] selectionArgs, String orderBy) {
101             mContentResolver = contentResolver;
102             mUri = uri;
103             mProjection = projection;
104             mSelection = selection;
105             mSelectionArgs = selectionArgs;
106             mOrderBy = orderBy;
107         }
108 
109         @Override
doInBackground(Void... params)110         protected final Result doInBackground(Void... params) {
111             if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) {
112                 IllegalStateException e = new IllegalStateException(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 (DEBUG) {
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             if (DEBUG) {
127                 Log.v(TAG, "Starting query for " + this);
128             }
129             try (Cursor c = mContentResolver
130                     .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
131                 if (c != null && !isCancelled()) {
132                     Result result = onQuery(c);
133                     if (DEBUG) {
134                         Log.v(TAG, "Finished query for " + this);
135                     }
136                     return result;
137                 } else {
138                     if (c == null) {
139                         Log.e(TAG, "Unknown query error for " + this);
140                     } else {
141                         if (DEBUG) {
142                             Log.d(TAG, "Canceled query for " + this);
143                         }
144                     }
145                     return null;
146                 }
147             } catch (Exception e) {
148                 SoftPreconditions.warn(TAG, null, "Error querying " + this, e);
149                 return null;
150             }
151         }
152 
153         /**
154          * Return the result from the cursor.
155          *
156          * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
157          */
158         @WorkerThread
onQuery(Cursor c)159         protected abstract Result onQuery(Cursor c);
160 
161         @Override
toString()162         public String toString() {
163             return this.getClass().getSimpleName() + "(" + mUri + ")";
164         }
165     }
166 
167     /**
168      * Returns the result of a query as an {@link List} of {@code T}.
169      *
170      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
171      *
172      * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)}
173      */
174     public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
175 
AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)176         public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
177                 String selection, String[] selectionArgs, String orderBy) {
178             super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
179         }
180 
181         @Override
onQuery(Cursor c)182         protected final List<T> onQuery(Cursor c) {
183             List<T> result = new ArrayList<>();
184             while (c.moveToNext()) {
185                 if (isCancelled()) {
186                     // This is guaranteed to never call onPostExecute because the task is canceled.
187                     return null;
188                 }
189                 T t = fromCursor(c);
190                 result.add(t);
191             }
192             if (DEBUG) {
193                 Log.v(TAG, "Found " + result.size() + " for  " + this);
194             }
195             return result;
196         }
197 
198         /**
199          * Return a single instance of {@code T} from the cursor.
200          *
201          * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
202          * #onQuery(Cursor)}.
203          *
204          * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
205          *
206          * @param c The cursor with the values to create T from.
207          */
208         @WorkerThread
fromCursor(Cursor c)209         protected abstract T fromCursor(Cursor c);
210     }
211 
212     /**
213      * Returns the result of a query as a single instance of {@code T}.
214      *
215      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
216      */
217     public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> {
218 
AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)219         public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection,
220                 String selection, String[] selectionArgs, String orderBy) {
221             super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
222         }
223 
224         @Override
onQuery(Cursor c)225         protected final T onQuery(Cursor c) {
226             if (c.moveToNext()) {
227                 if (isCancelled()) {
228                     // This is guaranteed to never call onPostExecute because the task is canceled.
229                     return null;
230                 }
231                 T result = fromCursor(c);
232                 if (c.moveToNext()) {
233                     Log.w(TAG, "More than one result for found for  " + this);
234                 }
235                 return result;
236             } else {
237                 if (DEBUG) {
238                     Log.v(TAG, "No result for found  for  " + this);
239                 }
240                 return null;
241             }
242 
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      * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
261      */
262     public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
263 
AsyncChannelQueryTask(ContentResolver contentResolver)264         public AsyncChannelQueryTask(ContentResolver contentResolver) {
265             super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
266                     null, null, null);
267         }
268 
269         @Override
fromCursor(Cursor c)270         protected final Channel fromCursor(Cursor c) {
271             return Channel.fromCursor(c);
272         }
273     }
274 
275     /**
276      * Execute the task on the {@link #DB_EXECUTOR} thread.
277      */
278     @SafeVarargs
279     @MainThread
executeOnDbThread(Params... params)280     public final void executeOnDbThread(Params... params) {
281         executeOnExecutor(DB_EXECUTOR, params);
282     }
283 
284     /**
285      * Gets an {@link List} of {@link Program}s for a given channel and period {@link
286      * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is
287      * {@code null}, then all the programs is queried.
288      */
289     public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> {
290         protected final Range<Long> mPeriod;
291         protected final long mChannelId;
292 
LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, @Nullable Range<Long> period)293         public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
294                 @Nullable Range<Long> period) {
295             super(contentResolver, period == null
296                     ? TvContract.buildProgramsUriForChannel(channelId)
297                     : TvContract.buildProgramsUriForChannel(channelId, period.getLower(),
298                             period.getUpper()),
299                     Program.PROJECTION, null, null, null);
300             mPeriod = period;
301             mChannelId = channelId;
302         }
303 
304         @Override
fromCursor(Cursor c)305         protected final Program fromCursor(Cursor c) {
306             return Program.fromCursor(c);
307         }
308 
getChannelId()309         public long getChannelId() {
310             return mChannelId;
311         }
312 
getPeriod()313         public final Range<Long> getPeriod() {
314             return mPeriod;
315         }
316     }
317 }
318