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