/* * 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.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 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. * *

Instances of this class should only be executed this using {@link * #executeOnDbThread(Object[])}. * * @param the type of the parameters sent to the task upon execution. * @param the type of the progress units published during the background computation. * @param the type of the result of the background computation. */ public abstract class AsyncDbTask extends AsyncTask { private static final String TAG = "AsyncDbTask"; private static final boolean DEBUG = false; public 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. * *

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)}. * *

{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} * which is implemented by subclasses. * * @param the type of result returned by {@link #onQuery(Cursor)} */ public abstract static class AsyncQueryTask extends AsyncDbTask { 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. * *

Note 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().getSimpleName() + "(" + mUri + ")"; } } /** * Returns the result of a query as an {@link List} of {@code T}. * *

Subclasses must implement {@link #fromCursor(Cursor)}. * * @param the type of result returned in a list by {@link #onQuery(Cursor)} */ public abstract static class AsyncQueryListTask extends AsyncQueryTask> { public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) { super(contentResolver, uri, projection, selection, selectionArgs, orderBy); } @Override protected final List onQuery(Cursor c) { List result = new ArrayList<>(); while (c.moveToNext()) { if (isCancelled()) { // This is guaranteed to never call onPostExecute because the task is canceled. return null; } 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. * *

NOTE Do not move the cursor or close it, that is handled by {@link * #onQuery(Cursor)}. * *

Note 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}. * *

Subclasses must implement {@link #fromCursor(Cursor)}. */ public abstract static class AsyncQueryItemTask extends AsyncQueryTask { 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. * *

NOTE Do not move the cursor or close it, that is handled by {@link * #onQuery(Cursor)}. * *

Note 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 { 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); } } /** * 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 AsyncQueryListTask { protected final Range mPeriod; protected final long mChannelId; public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId, @Nullable Range period) { super(contentResolver, period == null ? TvContract.buildProgramsUriForChannel(channelId) : TvContract.buildProgramsUriForChannel(channelId, period.getLower(), period.getUpper()), Program.PROJECTION, null, null, null); mPeriod = period; mChannelId = channelId; } @Override protected final Program fromCursor(Cursor c) { return Program.fromCursor(c); } public long getChannelId() { return mChannelId; } public final Range getPeriod() { return mPeriod; } } }