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 import com.android.tv.TvSingletons;
     31 import com.android.tv.common.BuildConfig;
     32 import com.android.tv.common.SoftPreconditions;
     33 import com.android.tv.data.ChannelImpl;
     34 import com.android.tv.data.Program;
     35 import com.android.tv.data.api.Channel;
     36 import com.android.tv.dvr.data.RecordedProgram;
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.concurrent.Executor;
     40 
     41 /**
     42  * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
     43  *
     44  * @param  the type of the parameters sent to the task upon execution.
     45  * @param  the type of the progress units published during the background computation.
     46  * @param  the type of the result of the background computation.
     47  */
     48 public abstract class AsyncDbTask<Params, Progress, Result>
     49         extends AsyncTask<Params, Progress, Result> {
     50     private static final String TAG = "AsyncDbTask";
     51     private static final boolean DEBUG = false;
     52 
     53     private final Executor mExecutor;
     54     boolean mCalledExecuteOnDbThread;
     55 
     56     protected AsyncDbTask(Executor mExecutor) {
     57         this.mExecutor = mExecutor;
     58     }
     59 
     60     /**
     61      * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
     62      * String)}.
     63      *
     64      * <p>{@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)} which
     65      * is implemented by subclasses.
     66      *
     67      * @param  the type of result returned by {@link #onQuery(Cursor)}
     68      */
     69     public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
     70         private final ContentResolver mContentResolver;
     71         private final Uri mUri;
     72         private final String[] mProjection;
     73         private final String mSelection;
     74         private final String[] mSelectionArgs;
     75         private final String mOrderBy;
     76 
     77         public AsyncQueryTask(
     78                 Executor executor,
     79                 ContentResolver contentResolver,
     80                 Uri uri,
     81                 String[] projection,
     82                 String selection,
     83                 String[] selectionArgs,
     84                 String orderBy) {
     85             super(executor);
     86             mContentResolver = contentResolver;
     87             mUri = uri;
     88             mProjection = projection;
     89             mSelection = selection;
     90             mSelectionArgs = selectionArgs;
     91             mOrderBy = orderBy;
     92         }
     93 
     94         @Override
     95         protected final Result doInBackground(Void... params) {
     96             if (!mCalledExecuteOnDbThread) {
     97                 IllegalStateException e =
     98                         new IllegalStateException(
     99                                 this
    100                                         + " should only be executed using executeOnDbThread, "
    101                                         + "but it was called on thread "
    102                                         + Thread.currentThread());
    103                 Log.w(TAG, e);
    104                 if (BuildConfig.ENG) {
    105                     throw e;
    106                 }
    107             }
    108 
    109             if (isCancelled()) {
    110                 // This is guaranteed to never call onPostExecute because the task is canceled.
    111                 return null;
    112             }
    113             if (DEBUG) {
    114                 Log.v(TAG, "Starting query for " + this);
    115             }
    116             try (Cursor c =
    117                     mContentResolver.query(
    118                             mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
    119                 if (c != null && !isCancelled()) {
    120                     Result result = onQuery(c);
    121                     if (DEBUG) {
    122                         Log.v(TAG, "Finished query for " + this);
    123                     }
    124                     return result;
    125                 } else {
    126                     if (c == null) {
    127                         Log.e(TAG, "Unknown query error for " + this);
    128                     } else {
    129                         if (DEBUG) {
    130                             Log.d(TAG, "Canceled query for " + this);
    131                         }
    132                     }
    133                     return null;
    134                 }
    135             } catch (Exception e) {
    136                 SoftPreconditions.warn(TAG, null, e, "Error querying " + this);
    137                 return null;
    138             }
    139         }
    140 
    141         /**
    142          * Return the result from the cursor.
    143          *
    144          * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
    145          */
    146         @WorkerThread
    147         protected abstract Result onQuery(Cursor c);
    148 
    149         @Override
    150         public String toString() {
    151             return this.getClass().getName() + "(" + mUri + ")";
    152         }
    153     }
    154 
    155     /**
    156      * Returns the result of a query as an {@link List} of {@code T}.
    157      *
    158      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
    159      *
    160      * @param <T> the type of result returned in a list by {@link #onQuery(Cursor)}
    161      */
    162     public abstract static class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
    163         private final CursorFilter mFilter;
    164 
    165         public AsyncQueryListTask(
    166                 Executor executor,
    167                 ContentResolver contentResolver,
    168                 Uri uri,
    169                 String[] projection,
    170                 String selection,
    171                 String[] selectionArgs,
    172                 String orderBy) {
    173             this(
    174                     executor,
    175                     contentResolver,
    176                     uri,
    177                     projection,
    178                     selection,
    179                     selectionArgs,
    180                     orderBy,
    181                     null);
    182         }
    183 
    184         public AsyncQueryListTask(
    185                 Executor executor,
    186                 ContentResolver contentResolver,
    187                 Uri uri,
    188                 String[] projection,
    189                 String selection,
    190                 String[] selectionArgs,
    191                 String orderBy,
    192                 CursorFilter filter) {
    193             super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
    194             mFilter = filter;
    195         }
    196 
    197         @Override
    198         protected final List<T> onQuery(Cursor c) {
    199             List<T> result = new ArrayList<>();
    200             while (c.moveToNext()) {
    201                 if (isCancelled()) {
    202                     // This is guaranteed to never call onPostExecute because the task is canceled.
    203                     return null;
    204                 }
    205                 if (mFilter != null && !mFilter.filter(c)) {
    206                     continue;
    207                 }
    208                 T t = fromCursor(c);
    209                 result.add(t);
    210             }
    211             if (DEBUG) {
    212                 Log.v(TAG, "Found " + result.size() + " for  " + this);
    213             }
    214             return result;
    215         }
    216 
    217         /**
    218          * Return a single instance of {@code T} from the cursor.
    219          *
    220          * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
    221          * #onQuery(Cursor)}.
    222          *
    223          * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
    224          *
    225          * @param c The cursor with the values to create T from.
    226          */
    227         @WorkerThread
    228         protected abstract T fromCursor(Cursor c);
    229     }
    230 
    231     /**
    232      * Returns the result of a query as a single instance of {@code T}.
    233      *
    234      * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
    235      */
    236     public abstract static class AsyncQueryItemTask<T> extends AsyncQueryTask<T> {
    237 
    238         public AsyncQueryItemTask(
    239                 Executor executor,
    240                 ContentResolver contentResolver,
    241                 Uri uri,
    242                 String[] projection,
    243                 String selection,
    244                 String[] selectionArgs,
    245                 String orderBy) {
    246             super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy);
    247         }
    248 
    249         @Override
    250         protected final T onQuery(Cursor c) {
    251             if (c.moveToNext()) {
    252                 if (isCancelled()) {
    253                     // This is guaranteed to never call onPostExecute because the task is canceled.
    254                     return null;
    255                 }
    256                 T result = fromCursor(c);
    257                 if (c.moveToNext()) {
    258                     Log.w(TAG, "More than one result for found for  " + this);
    259                 }
    260                 return result;
    261             } else {
    262                 if (DEBUG) {
    263                     Log.v(TAG, "No result for found  for  " + this);
    264                 }
    265                 return null;
    266             }
    267         }
    268 
    269         /**
    270          * Return a single instance of {@code T} from the cursor.
    271          *
    272          * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
    273          * #onQuery(Cursor)}.
    274          *
    275          * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
    276          *
    277          * @param c The cursor with the values to create T from.
    278          */
    279         @WorkerThread
    280         protected abstract T fromCursor(Cursor c);
    281     }
    282 
    283     /** Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}. */
    284     public abstract static class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
    285 
    286         public AsyncChannelQueryTask(Executor executor, ContentResolver contentResolver) {
    287             super(
    288                     executor,
    289                     contentResolver,
    290                     TvContract.Channels.CONTENT_URI,
    291                     ChannelImpl.PROJECTION,
    292                     null,
    293                     null,
    294                     null);
    295         }
    296 
    297         @Override
    298         protected final Channel fromCursor(Cursor c) {
    299             return ChannelImpl.fromCursor(c);
    300         }
    301     }
    302 
    303     /** Gets an {@link List} of {@link Program}s from {@link TvContract.Programs#CONTENT_URI}. */
    304     public abstract static class AsyncProgramQueryTask extends AsyncQueryListTask<Program> {
    305         public AsyncProgramQueryTask(Executor executor, ContentResolver contentResolver) {
    306             super(
    307                     executor,
    308                     contentResolver,
    309                     Programs.CONTENT_URI,
    310                     Program.PROJECTION,
    311                     null,
    312                     null,
    313                     null);
    314         }
    315 
    316         public AsyncProgramQueryTask(
    317                 Executor executor,
    318                 ContentResolver contentResolver,
    319                 Uri uri,
    320                 String selection,
    321                 String[] selectionArgs,
    322                 String sortOrder,
    323                 CursorFilter filter) {
    324             super(
    325                     executor,
    326                     contentResolver,
    327                     uri,
    328                     Program.PROJECTION,
    329                     selection,
    330                     selectionArgs,
    331                     sortOrder,
    332                     filter);
    333         }
    334 
    335         @Override
    336         protected final Program fromCursor(Cursor c) {
    337             return Program.fromCursor(c);
    338         }
    339     }
    340 
    341     /** Gets an {@link List} of {@link TvContract.RecordedPrograms}s. */
    342     public abstract static class AsyncRecordedProgramQueryTask
    343             extends AsyncQueryListTask<RecordedProgram> {
    344         public AsyncRecordedProgramQueryTask(
    345                 Executor executor, ContentResolver contentResolver, Uri uri) {
    346             super(executor, contentResolver, uri, RecordedProgram.PROJECTION, null, null, null);
    347         }
    348 
    349         @Override
    350         protected final RecordedProgram fromCursor(Cursor c) {
    351             return RecordedProgram.fromCursor(c);
    352         }
    353     }
    354 
    355     /** Execute the task on {@link TvSingletons#getDbExecutor()}. */
    356     @SafeVarargs
    357     @MainThread
    358     public final void executeOnDbThread(Params... params) {
    359         mCalledExecuteOnDbThread = true;
    360         executeOnExecutor(mExecutor, params);
    361     }
    362 
    363     /**
    364      * Gets an {@link List} of {@link Program}s for a given channel and period {@link
    365      * TvContract#buildProgramsUriForChannel(long, long, long)}. If the {@code period} is {@code
    366      * null}, then all the programs is queried.
    367      */
    368     public static class LoadProgramsForChannelTask extends AsyncProgramQueryTask {
    369         protected final Range<Long> mPeriod;
    370         protected final long mChannelId;
    371 
    372         public LoadProgramsForChannelTask(
    373                 Executor executor,
    374                 ContentResolver contentResolver,
    375                 long channelId,
    376                 @Nullable Range<Long> period) {
    377             super(
    378                     executor,
    379                     contentResolver,
    380                     period == null
    381                             ? TvContract.buildProgramsUriForChannel(channelId)
    382                             : TvContract.buildProgramsUriForChannel(
    383                                     channelId, period.getLower(), period.getUpper()),
    384                     null,
    385                     null,
    386                     null,
    387                     null);
    388             mPeriod = period;
    389             mChannelId = channelId;
    390         }
    391 
    392         public long getChannelId() {
    393             return mChannelId;
    394         }
    395 
    396         public final Range<Long> getPeriod() {
    397             return mPeriod;
    398         }
    399     }
    400 
    401     /** Gets a single {@link Program} from {@link TvContract.Programs#CONTENT_URI}. */
    402     public static class AsyncQueryProgramTask extends AsyncQueryItemTask<Program> {
    403 
    404         public AsyncQueryProgramTask(
    405                 Executor executor, ContentResolver contentResolver, long programId) {
    406             super(
    407                     executor,
    408                     contentResolver,
    409                     TvContract.buildProgramUri(programId),
    410                     Program.PROJECTION,
    411                     null,
    412                     null,
    413                     null);
    414         }
    415 
    416         @Override
    417         protected Program fromCursor(Cursor c) {
    418             return Program.fromCursor(c);
    419         }
    420     }
    421 
    422     /** An interface which filters the row. */
    423     public interface CursorFilter extends Filter<Cursor> {}
    424 }
    425