| /* |
| * 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.content.Context; |
| 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.TvSingletons; |
| import com.android.tv.common.BuildConfig; |
| import com.android.tv.common.SoftPreconditions; |
| import com.android.tv.data.ChannelImpl; |
| import com.android.tv.data.ProgramImpl; |
| import com.android.tv.data.api.Channel; |
| import com.android.tv.data.api.Program; |
| import com.android.tv.dvr.data.RecordedProgram; |
| |
| import com.google.common.base.Predicate; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| import javax.inject.Qualifier; |
| |
| /** |
| * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service. |
| * |
| * @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; |
| |
| /** Annotation for requesting the {@link Executor} for data base access. */ |
| @Qualifier |
| public @interface DbExecutor {} |
| |
| private final Executor mExecutor; |
| boolean mCalledExecuteOnDbThread; |
| |
| protected AsyncDbTask(Executor mExecutor) { |
| this.mExecutor = mExecutor; |
| } |
| |
| /** |
| * 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 WeakReference<Context> mContextReference; |
| private final Uri mUri; |
| private final String mSelection; |
| private final String[] mSelectionArgs; |
| private final String mOrderBy; |
| private String[] mProjection; |
| |
| public AsyncQueryTask( |
| @DbExecutor Executor executor, |
| Context context, |
| Uri uri, |
| String[] projection, |
| String selection, |
| String[] selectionArgs, |
| String orderBy) { |
| super(executor); |
| mContextReference = new WeakReference<>(context); |
| mUri = uri; |
| mProjection = projection; |
| mSelection = selection; |
| mSelectionArgs = selectionArgs; |
| mOrderBy = orderBy; |
| } |
| |
| @Override |
| protected final Result doInBackground(Void... params) { |
| if (!mCalledExecuteOnDbThread) { |
| IllegalStateException e = |
| new IllegalStateException( |
| this |
| + " should only be executed using executeOnDbThread, " |
| + "but it was called on thread " |
| + Thread.currentThread()); |
| Log.w(TAG, e); |
| if (BuildConfig.ENG) { |
| throw e; |
| } |
| } |
| |
| if (isCancelled()) { |
| // This is guaranteed to never call onPostExecute because the task is canceled. |
| return null; |
| } |
| Context context = mContextReference.get(); |
| if (context == null) { |
| return null; |
| } |
| if (Utils.isProgramsUri(mUri) |
| && TvProviderUtils.checkSeriesIdColumn(context, Programs.CONTENT_URI)) { |
| mProjection = |
| TvProviderUtils.addExtraColumnsToProjection( |
| mProjection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID); |
| } else if (Utils.isRecordedProgramsUri(mUri)) { |
| if (TvProviderUtils.checkSeriesIdColumn( |
| context, TvContract.RecordedPrograms.CONTENT_URI)) { |
| mProjection = |
| TvProviderUtils.addExtraColumnsToProjection( |
| mProjection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_SERIES_ID); |
| } |
| if (TvProviderUtils.checkStateColumn( |
| context, TvContract.RecordedPrograms.CONTENT_URI)) { |
| mProjection = |
| TvProviderUtils.addExtraColumnsToProjection( |
| mProjection, TvProviderUtils.EXTRA_PROGRAM_COLUMN_STATE); |
| } |
| } |
| if (DEBUG) { |
| Log.v(TAG, "Starting query for " + this); |
| } |
| try (Cursor c = |
| context.getContentResolver() |
| .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, e, "Error querying " + this); |
| 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( |
| Executor executor, |
| Context context, |
| Uri uri, |
| String[] projection, |
| String selection, |
| String[] selectionArgs, |
| String orderBy) { |
| this(executor, context, uri, projection, selection, selectionArgs, orderBy, null); |
| } |
| |
| public AsyncQueryListTask( |
| Executor executor, |
| Context context, |
| Uri uri, |
| String[] projection, |
| String selection, |
| String[] selectionArgs, |
| String orderBy, |
| CursorFilter filter) { |
| super(executor, context, 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.apply(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( |
| Executor executor, |
| Context context, |
| Uri uri, |
| String[] projection, |
| String selection, |
| String[] selectionArgs, |
| String orderBy) { |
| super(executor, context, 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(Executor executor, Context context) { |
| super( |
| executor, |
| context, |
| TvContract.Channels.CONTENT_URI, |
| ChannelImpl.PROJECTION, |
| null, |
| null, |
| null); |
| } |
| |
| @Override |
| protected final Channel fromCursor(Cursor c) { |
| return ChannelImpl.fromCursor(c); |
| } |
| } |
| |
| /** |
| * Gets an {@link List} of {@link ProgramImpl}s from {@link TvContract.Programs#CONTENT_URI}. |
| */ |
| public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> { |
| public AsyncProgramQueryTask(Executor executor, Context context) { |
| super( |
| executor, |
| context, |
| Programs.CONTENT_URI, |
| ProgramImpl.PROJECTION, |
| null, |
| null, |
| null); |
| } |
| |
| public AsyncProgramQueryTask( |
| Executor executor, |
| Context context, |
| Uri uri, |
| String selection, |
| String[] selectionArgs, |
| String sortOrder, |
| CursorFilter filter) { |
| super( |
| executor, |
| context, |
| uri, |
| ProgramImpl.PROJECTION, |
| selection, |
| selectionArgs, |
| sortOrder, |
| filter); |
| } |
| |
| @Override |
| protected final Program fromCursor(Cursor c) { |
| return ProgramImpl.fromCursor(c); |
| } |
| } |
| |
| /** Gets an {@link List} of {@link TvContract.RecordedPrograms}s. */ |
| public abstract static class AsyncRecordedProgramQueryTask |
| extends AsyncQueryListTask<RecordedProgram> { |
| public AsyncRecordedProgramQueryTask(Executor executor, Context context, Uri uri) { |
| super(executor, context, uri, RecordedProgram.PROJECTION, null, null, null); |
| } |
| |
| @Override |
| protected final RecordedProgram fromCursor(Cursor c) { |
| return RecordedProgram.fromCursor(c); |
| } |
| } |
| |
| /** Execute the task on {@link TvSingletons#getDbExecutor()}. */ |
| @SafeVarargs |
| @MainThread |
| public final void executeOnDbThread(Params... params) { |
| mCalledExecuteOnDbThread = true; |
| executeOnExecutor(mExecutor, params); |
| } |
| |
| /** |
| * Gets an {@link List} of {@link ProgramImpl}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( |
| Executor executor, Context context, long channelId, @Nullable Range<Long> period) { |
| super( |
| executor, |
| context, |
| 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 ProgramImpl} from {@link TvContract.Programs#CONTENT_URI}. */ |
| public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> { |
| |
| public AsyncQueryProgramTask(Executor executor, Context context, long programId) { |
| super( |
| executor, |
| context, |
| TvContract.buildProgramUri(programId), |
| ProgramImpl.PROJECTION, |
| null, |
| null, |
| null); |
| } |
| |
| @Override |
| protected Program fromCursor(Cursor c) { |
| return ProgramImpl.fromCursor(c); |
| } |
| } |
| |
| /** An interface which filters the row. */ |
| public interface CursorFilter extends Predicate<Cursor> {} |
| } |