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;
30
31import com.android.tv.common.SoftPreconditions;
32import com.android.tv.data.Channel;
33import com.android.tv.data.Program;
34import com.android.tv.dvr.data.RecordedProgram;
35
36import java.util.ArrayList;
37import java.util.List;
38import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40import 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  the type of the parameters sent to the task upon execution.
49 * @param  the type of the progress units published during the background computation.
50 * @param  the type of the result of the background computation.
51 */
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 = 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     */
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     */
79    public static void executeOnDbThread(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  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
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
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
161        protected abstract Result onQuery(Cursor c);
162
163        @Override
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
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
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
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
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
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
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
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
276        public AsyncChannelQueryTask(ContentResolver contentResolver) {
277            super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
278                    null, null, null);
279        }
280
281        @Override
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> {
291        public AsyncProgramQueryTask(ContentResolver contentResolver) {
292            super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null);
293        }
294
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
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> {
312        public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) {
313            super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
314        }
315
316        @Override
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
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
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
351        public long getChannelId() {
352            return mChannelId;
353        }
354
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
365        public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) {
366            super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null,
367                    null, null);
368        }
369
370        @Override
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