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