blob: 3cc91e409816a90659e759caf83cbe0ef4b28a65 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.util;
import android.content.ContentResolver;
import android.database.Cursor;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.Range;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.dvr.data.RecordedProgram;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
/**
* {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
*
* <p>Instances of this class should only be executed this using {@link
* #executeOnDbThread(Object[])}.
*
* @param <Params> the type of the parameters sent to the task upon execution.
* @param <Progress> the type of the progress units published during the background computation.
* @param <Result> the type of the result of the background computation.
*/
public abstract class AsyncDbTask<Params, Progress, Result>
extends AsyncTask<Params, Progress, Result> {
private static final String TAG = "AsyncDbTask";
private static final boolean DEBUG = false;
private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory(
AsyncDbTask.class.getSimpleName());
private static final ExecutorService DB_EXECUTOR = Executors
.newSingleThreadExecutor(THREAD_FACTORY);
/**
* Returns the single tread executor used for DbTasks.
*/
public static ExecutorService getExecutor() {
return DB_EXECUTOR;
}
/**
* Executes the given command at some time in the future.
*
* <p>The command will be executed by {@link #getExecutor()}.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
public static void execute(Runnable command) {
DB_EXECUTOR.execute(command);
}
/**
* Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
* String)}.
*
* <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
* which is implemented by subclasses.
*
* @param <Result> the type of result returned by {@link #onQuery(Cursor)}
*/
public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
private final ContentResolver mContentResolver;
private final Uri mUri;
private final String[] mProjection;
private final String mSelection;
private final String[] mSelectionArgs;
private final String mOrderBy;
public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy) {
mContentResolver = contentResolver;
mUri = uri;
mProjection = projection;
mSelection = selection;
mSelectionArgs = selectionArgs;
mOrderBy = orderBy;
}
@Override
protected final Result doInBackground(Void... params) {
if (!THREAD_FACTORY.namedWithPrefix(Thread.currentThread())) {
IllegalStateException e = new IllegalStateException(this
+ " should only be executed using executeOnDbThread, "
+ "but it was called on thread "
+ Thread.currentThread());
Log.w(TAG, e);
if (DEBUG) {
throw e;
}
}
if (isCancelled()) {
// This is guaranteed to never call onPostExecute because the task is canceled.
return null;
}
if (DEBUG) {
Log.v(TAG, "Starting query for " + this);
}
try (Cursor c = mContentResolver
.query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
if (c != null && !isCancelled()) {
Result result = onQuery(c);
if (DEBUG) {
Log.v(TAG, "Finished query for " + this);
}
return result;
} else {
if (c == null) {
Log.e(TAG, "Unknown query error for " + this);
} else {
if (DEBUG) {
Log.d(TAG, "Canceled query for " + this);
}
}
return null;
}
} catch (Exception e) {
SoftPreconditions.warn(TAG, null, "Error querying " + this, e);
return null;
}
}
/**
* Return the result from the cursor.
*
* <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
*/
@WorkerThread
protected abstract Result onQuery(Cursor c);
@Override
public String toString() {
return this.getClass().getName() + "(" + mUri + ")";
}
}
/**
* Returns the result of a query as an {@link List} of {@code T}.
*
* <p>Subclasses must implement {@link #fromCursor(Cursor)}.
*
* @param <T> the type of result returned in a list by {@link #onQuery(Cursor)}
*/
public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
private final CursorFilter mFilter;
public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy) {
this(contentResolver, uri, projection, selection, selectionArgs, orderBy, null);
}
public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy, CursorFilter filter) {
super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
mFilter = filter;
}
@Override
protected final List<T> onQuery(Cursor c) {
List<T> result = new ArrayList<>();
while (c.moveToNext()) {
if (isCancelled()) {
// This is guaranteed to never call onPostExecute because the task is canceled.
return null;
}
if (mFilter != null && !mFilter.filter(c)) {
continue;
}
T t = fromCursor(c);
result.add(t);
}
if (DEBUG) {
Log.v(TAG, "Found " + result.size() + " for " + this);
}
return result;
}
/**
* Return a single instance of {@code T} from the cursor.
*
* <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
* #onQuery(Cursor)}.
*
* <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
*
* @param c The cursor with the values to create T from.
*/
@WorkerThread
protected abstract T fromCursor(Cursor c);
}
/**
* Returns the result of a query as a single instance of {@code T}.
*
* <p>Subclasses must implement {@link #fromCursor(Cursor)}.
*/
public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> {
public AsyncQueryItemTask(ContentResolver contentResolver, Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy) {
super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
}
@Override
protected final T onQuery(Cursor c) {
if (c.moveToNext()) {
if (isCancelled()) {
// This is guaranteed to never call onPostExecute because the task is canceled.
return null;
}
T result = fromCursor(c);
if (c.moveToNext()) {
Log.w(TAG, "More than one result for found for " + this);
}
return result;
} else {
if (DEBUG) {
Log.v(TAG, "No result for found for " + this);
}
return null;
}
}
/**
* Return a single instance of {@code T} from the cursor.
*
* <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
* #onQuery(Cursor)}.
*
* <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
*
* @param c The cursor with the values to create T from.
*/
@WorkerThread
protected abstract T fromCursor(Cursor c);
}
/**
* Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
*/
public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
public AsyncChannelQueryTask(ContentResolver contentResolver) {
super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
null, null, null);
}
@Override
protected final Channel fromCursor(Cursor c) {
return Channel.fromCursor(c);
}
}
/**
* Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}.
*/
public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> {
public AsyncProgramQueryTask(ContentResolver contentResolver) {
super(contentResolver, Programs.CONTENT_URI, Program.PROJECTION, null, null, null);
}
public AsyncProgramQueryTask(ContentResolver contentResolver, Uri uri, String selection,
String[] selectionArgs, String sortOrder, CursorFilter filter) {
super(contentResolver, uri, Program.PROJECTION, selection, selectionArgs, sortOrder,
filter);
}
@Override
protected final Program fromCursor(Cursor c) {
return Program.fromCursor(c);
}
}
/**
* Gets an {@link List} of {@link TvContract.RecordedPrograms}s.
*/
public abstract static class AsyncRecordedProgramQueryTask
extends AsyncQueryListTask<RecordedProgram> {
public AsyncRecordedProgramQueryTask(ContentResolver contentResolver, Uri uri) {
super(contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
}
@Override
protected final RecordedProgram fromCursor(Cursor c) {
return RecordedProgram.fromCursor(c);
}
}
/**
* Execute the task on the {@link #DB_EXECUTOR} thread.
*/
@SafeVarargs
@MainThread
public final void executeOnDbThread(Params... params) {
executeOnExecutor(DB_EXECUTOR, params);
}
/**
* Gets an {@link List} of {@link Program}s for a given channel and period {@link
* TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is
* {@code null}, then all the programs is queried.
*/
public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask {
protected final Range<Long> mPeriod;
protected final long mChannelId;
public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
@Nullable Range<Long> period) {
super(contentResolver, period == null
? TvContract.buildProgramsUriForChannel(channelId)
: TvContract.buildProgramsUriForChannel(channelId, period.getLower(),
period.getUpper()),
null, null, null, null);
mPeriod = period;
mChannelId = channelId;
}
public long getChannelId() {
return mChannelId;
}
public final Range<Long> getPeriod() {
return mPeriod;
}
}
/**
* Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}.
*/
public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> {
public AsyncQueryProgramTask(ContentResolver contentResolver, long programId) {
super(contentResolver, TvContract.buildProgramUri(programId), Program.PROJECTION, null,
null, null);
}
@Override
protected Program fromCursor(Cursor c) {
return Program.fromCursor(c);
}
}
/**
* An interface which filters the row.
*/
public interface CursorFilter extends Filter<Cursor> { }
}