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