AsyncDbTask.java revision 3a72b93e554bd22a5c64e71a6956d9604ce05108
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.net.Uri;
23import android.os.AsyncTask;
24import android.support.annotation.MainThread;
25import android.support.annotation.WorkerThread;
26import android.util.Log;
27import android.util.Range;
28
29import com.android.tv.data.Channel;
30import com.android.tv.data.Program;
31
32import java.util.ArrayList;
33import java.util.List;
34import java.util.concurrent.ExecutorService;
35import java.util.concurrent.Executors;
36import java.util.concurrent.RejectedExecutionException;
37
38/**
39 * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
40 *
41 * <p>Instances of this class should only be executed this using {@link
42 * #executeOnDbThread(Object[])}.
43 */
44public abstract class AsyncDbTask<Params, Progress, Result>
45        extends AsyncTask<Params, Progress, Result> {
46    private static final String TAG = "AsyncDbTask";
47    private static final boolean DEBUG = false;
48
49    public static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
50            AsyncDbTask.class.getSimpleName());
51    private static final ExecutorService DB_EXECUTOR = Executors
52            .newSingleThreadExecutor(THREAD_FACTORY);
53
54    /**
55     * Returns the single tread executor used for DbTasks.
56     */
57    public static ExecutorService getExecutor() {
58        return DB_EXECUTOR;
59    }
60
61    /**
62     * Executes the given command at some time in the future.
63     *
64     * <p>The command will be executed by {@link #getExecutor()}.
65     *
66     * @param command the runnable task
67     * @throws RejectedExecutionException if this task cannot be
68     *                                    accepted for execution
69     * @throws NullPointerException       if command is null
70     */
71    public static void execute(Runnable command) {
72        DB_EXECUTOR.execute(command);
73    }
74
75    /**
76     * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
77     * String)}.
78     *
79     * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
80     * which is implemented by subclasses.
81     *
82     * @param  The type of result returned by {@link #onQuery(Cursor)}
83     */
84    public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
85        private final ContentResolver mContentResolver;
86        private final Uri mUri;
87        private final String[] mProjection;
88        private final String mSelection;
89        private final String[] mSelectionArgs;
90        private final String mOrderBy;
91
92
93        public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
94                String selection, String[] selectionArgs, String orderBy) {
95            mContentResolver = contentResolver;
96            mUri = uri;
97            mProjection = projection;
98            mSelection = selection;
99            mSelectionArgs = selectionArgs;
100            mOrderBy = orderBy;
101        }
102
103        @Override
104        protected final Result doInBackground(Void... params) {
105            if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) {
106                IllegalStateException e = new IllegalStateException(
107                        this + " should only be executed using executeOnDbThread, " +
108                                "but it was called on thread " + Thread.currentThread());
109                Log.w(TAG, e);
110                if (DEBUG) {
111                    throw e;
112                }
113            }
114
115            if (isCancelled()) {
116                // This is guaranteed to never call onPostExecute because the task is canceled.
117                return null;
118            }
119            if (DEBUG) {
120                Log.v(TAG, "Starting query for " + this);
121            }
122            try (Cursor c = mContentResolver
123                    .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
124                if (c != null && !isCancelled()) {
125                    Result result = onQuery(c);
126                    if (DEBUG) {
127                        Log.v(TAG, "Finished query for " + this);
128                    }
129                    return result;
130                } else {
131                    if (c == null) {
132                        Log.e(TAG, "Unknown query error for " + this);
133                    } else {
134                        if (DEBUG) {
135                            Log.d(TAG, "Canceled query for " + this);
136                        }
137                    }
138                    return null;
139                }
140            } catch (SecurityException e) {
141                Log.d(TAG, "Security exception during query", e);
142                return null;
143            }
144        }
145
146        /**
147         * Return the result from the cursor.
148         *
149         * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
150         */
151        @WorkerThread
152        protected abstract Result onQuery(Cursor c);
153
154        @Override
155        public String toString() {
156            return this.getClass().getSimpleName() + "(" + mUri + ")";
157        }
158    }
159
160    /**
161     * Returns the result of a query as an {@link List} of {@code T}.
162     *
163     * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
164     */
165    public static abstract class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
166
167        public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
168                String selection, String[] selectionArgs, String orderBy) {
169            super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
170        }
171
172        @Override
173        protected final List<T> onQuery(Cursor c) {
174            List<T> result = new ArrayList<>();
175            while (c.moveToNext()) {
176                if (isCancelled()) {
177                    // This is guaranteed to never call onPostExecute because the task is canceled.
178                    return null;
179                }
180                T t = fromCursor(c);
181                result.add(t);
182            }
183            if (DEBUG) {
184                Log.v(TAG, "Found " + result.size() + " for  " + this);
185            }
186            return result;
187        }
188
189        /**
190         * Return a single instance of {@code T} from the cursor.
191         *
192         * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
193         * #onQuery(Cursor)}.
194         *
195         * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
196         *
197         * @param c The cursor with the values to create T from.
198         */
199        @WorkerThread
200        protected abstract T fromCursor(Cursor c);
201    }
202
203    /**
204     * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
205     */
206    public static abstract class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
207
208        public AsyncChannelQueryTask(ContentResolver contentResolver) {
209            super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
210                    null, null, null);
211        }
212
213        @Override
214        protected final Channel fromCursor(Cursor c) {
215            return Channel.fromCursor(c);
216        }
217    }
218
219    /**
220     * Execute the task on the {@link #DB_EXECUTOR} thread.
221     */
222    @SafeVarargs
223    @MainThread
224    public final void executeOnDbThread(Params... params) {
225        executeOnExecutor(DB_EXECUTOR, params);
226    }
227
228    /**
229     * Gets an {@link List} of {@link Program}s for a given channel and period {@link
230     * TvContract#buildProgramsUriForChannel(long, long, long)}.
231     */
232    public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> {
233        protected final Range<Long> mPeriod;
234        protected final long mChannelId;
235
236        public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
237                Range<Long> period) {
238            super(contentResolver, TvContract
239                    .buildProgramsUriForChannel(channelId, period.getLower(), period.getUpper()),
240                    Program.PROJECTION, null, null, null);
241            mPeriod = period;
242            mChannelId = channelId;
243        }
244
245        @Override
246        protected final Program fromCursor(Cursor c) {
247            return Program.fromCursor(c);
248        }
249
250        public long getChannelId() {
251            return mChannelId;
252        }
253
254        public final Range<Long> getPeriod() {
255            return mPeriod;
256        }
257    }
258}
259