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.media.tv.TvContract.Programs;
     23 import android.net.Uri;
     24 import android.os.AsyncTask;
     25 import android.support.annotation.MainThread;
     26 import android.support.annotation.Nullable;
     27 import android.support.annotation.WorkerThread;
     28 import android.util.Log;
     29 import android.util.Range;
     30 
     31 import com.android.tv.common.SoftPreconditions;
     32 import com.android.tv.data.Channel;
     33 import com.android.tv.data.Program;
     34 import com.android.tv.dvr.data.RecordedProgram;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 import java.util.concurrent.ExecutorService;
     39 import java.util.concurrent.Executors;
     40 import 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  */
     52 public 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