Home | History | Annotate | Download | only in utility
      1 /*
      2  * Copyright (C) 2011 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.emailcommon.utility;
     18 
     19 import android.os.AsyncTask;
     20 
     21 import com.google.common.annotations.VisibleForTesting;
     22 
     23 import java.util.ArrayList;
     24 import java.util.LinkedList;
     25 import java.util.concurrent.ExecutionException;
     26 import java.util.concurrent.Executor;
     27 
     28 /**
     29  * {@link AsyncTask} substitution for the email app.
     30  *
     31  * Modeled after {@link AsyncTask}; the basic usage is the same, with extra features:
     32  * - Bulk cancellation of multiple tasks.  This is mainly used by UI to cancel pending tasks
     33  *   in onDestroy() or similar places.
     34  * - Instead of {@link AsyncTask#onPostExecute}, it has {@link #onSuccess(Object)}, as the
     35  *   regular {@link AsyncTask#onPostExecute} is a bit hard to predict when it'll be called and
     36  *   when it won't.
     37  *
     38  * Note this class is missing some of the {@link AsyncTask} features, e.g. it lacks
     39  * {@link AsyncTask#onProgressUpdate}.  Add these when necessary.
     40  */
     41 public abstract class EmailAsyncTask<Params, Progress, Result> {
     42     private static final Executor SERIAL_EXECUTOR = AsyncTask.SERIAL_EXECUTOR;
     43     private static final Executor PARALLEL_EXECUTOR = AsyncTask.THREAD_POOL_EXECUTOR;
     44 
     45     /**
     46      * Tracks {@link EmailAsyncTask}.
     47      *
     48      * Call {@link #cancelAllInterrupt()} to cancel all tasks registered.
     49      */
     50     public static class Tracker {
     51         private final LinkedList<EmailAsyncTask<?, ?, ?>> mTasks =
     52                 new LinkedList<EmailAsyncTask<?, ?, ?>>();
     53 
     54         private void add(EmailAsyncTask<?, ?, ?> task) {
     55             synchronized (mTasks) {
     56                 mTasks.add(task);
     57             }
     58         }
     59 
     60         private void remove(EmailAsyncTask<?, ?, ?> task) {
     61             synchronized (mTasks) {
     62                 mTasks.remove(task);
     63             }
     64         }
     65 
     66         /**
     67          * Cancel all registered tasks.
     68          */
     69         @VisibleForTesting
     70         public void cancelAllInterrupt() {
     71             synchronized (mTasks) {
     72                 for (EmailAsyncTask<?, ?, ?> task : mTasks) {
     73                     task.cancel(true);
     74                 }
     75                 mTasks.clear();
     76             }
     77         }
     78 
     79         /**
     80          * Cancel all instances of the same class as {@code current} other than
     81          * {@code current} itself.
     82          */
     83         /* package */ void cancelOthers(EmailAsyncTask<?, ?, ?> current) {
     84             final Class<?> clazz = current.getClass();
     85             synchronized (mTasks) {
     86                 final ArrayList<EmailAsyncTask<?, ?, ?>> toRemove =
     87                         new ArrayList<EmailAsyncTask<?, ?, ?>>();
     88                 for (EmailAsyncTask<?, ?, ?> task : mTasks) {
     89                     if ((task != current) && task.getClass().equals(clazz)) {
     90                         task.cancel(true);
     91                         toRemove.add(task);
     92                     }
     93                 }
     94                 for (EmailAsyncTask<?, ?, ?> task : toRemove) {
     95                     mTasks.remove(task);
     96                 }
     97             }
     98         }
     99 
    100         /* package */ int getTaskCountForTest() {
    101             return mTasks.size();
    102         }
    103 
    104         /* package */ boolean containsTaskForTest(EmailAsyncTask<?, ?, ?> task) {
    105             return mTasks.contains(task);
    106         }
    107     }
    108 
    109     private final Tracker mTracker;
    110 
    111     private static class InnerTask<Params2, Progress2, Result2>
    112             extends AsyncTask<Params2, Progress2, Result2> {
    113         private final EmailAsyncTask<Params2, Progress2, Result2> mOwner;
    114 
    115         public InnerTask(EmailAsyncTask<Params2, Progress2, Result2> owner) {
    116             mOwner = owner;
    117         }
    118 
    119         @Override
    120         protected Result2 doInBackground(Params2... params) {
    121             return mOwner.doInBackground(params);
    122         }
    123 
    124         @Override
    125         public void onCancelled(Result2 result) {
    126             mOwner.unregisterSelf();
    127             mOwner.onCancelled(result);
    128         }
    129 
    130         @Override
    131         public void onPostExecute(Result2 result) {
    132             mOwner.unregisterSelf();
    133             if (mOwner.mCancelled) {
    134                 mOwner.onCancelled(result);
    135             } else {
    136                 mOwner.onSuccess(result);
    137             }
    138         }
    139     }
    140 
    141     private final InnerTask<Params, Progress, Result> mInnerTask;
    142     private volatile boolean mCancelled;
    143 
    144     public EmailAsyncTask(Tracker tracker) {
    145         mTracker = tracker;
    146         if (mTracker != null) {
    147             mTracker.add(this);
    148         }
    149         mInnerTask = new InnerTask<Params, Progress, Result>(this);
    150     }
    151 
    152     /* package */ final void unregisterSelf() {
    153         if (mTracker != null) {
    154             mTracker.remove(this);
    155         }
    156     }
    157 
    158     /** @see AsyncTask#doInBackground */
    159     protected abstract Result doInBackground(Params... params);
    160 
    161 
    162     /** @see AsyncTask#cancel(boolean) */
    163     public final void cancel(boolean mayInterruptIfRunning) {
    164         mCancelled = true;
    165         mInnerTask.cancel(mayInterruptIfRunning);
    166     }
    167 
    168     /** @see AsyncTask#onCancelled */
    169     protected void onCancelled(Result result) {
    170     }
    171 
    172     /**
    173      * Similar to {@link AsyncTask#onPostExecute}, but this will never be executed if
    174      * {@link #cancel(boolean)} has been called before its execution, even if
    175      * {@link #doInBackground(Object...)} has completed when cancelled.
    176      *
    177      * @see AsyncTask#onPostExecute
    178      */
    179     protected void onSuccess(Result result) {
    180     }
    181 
    182     /**
    183      * execute on {@link #PARALLEL_EXECUTOR}
    184      *
    185      * @see AsyncTask#execute
    186      */
    187     public final EmailAsyncTask<Params, Progress, Result> executeParallel(Params... params) {
    188         return executeInternal(PARALLEL_EXECUTOR, false, params);
    189     }
    190 
    191     /**
    192      * execute on {@link #SERIAL_EXECUTOR}
    193      *
    194      * @see AsyncTask#execute
    195      */
    196     public final EmailAsyncTask<Params, Progress, Result> executeSerial(Params... params) {
    197         return executeInternal(SERIAL_EXECUTOR, false, params);
    198     }
    199 
    200     /**
    201      * Cancel all previously created instances of the same class tracked by the same
    202      * {@link Tracker}, and then {@link #executeParallel}.
    203      */
    204     public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteParallel(
    205             Params... params) {
    206         return executeInternal(PARALLEL_EXECUTOR, true, params);
    207     }
    208 
    209     /**
    210      * Cancel all previously created instances of the same class tracked by the same
    211      * {@link Tracker}, and then {@link #executeSerial}.
    212      */
    213     public final EmailAsyncTask<Params, Progress, Result> cancelPreviousAndExecuteSerial(
    214             Params... params) {
    215         return executeInternal(SERIAL_EXECUTOR, true, params);
    216     }
    217 
    218     private EmailAsyncTask<Params, Progress, Result> executeInternal(Executor executor,
    219             boolean cancelPrevious, Params... params) {
    220         if (cancelPrevious) {
    221             if (mTracker == null) {
    222                 throw new IllegalStateException();
    223             } else {
    224                 mTracker.cancelOthers(this);
    225             }
    226         }
    227         mInnerTask.executeOnExecutor(executor, params);
    228         return this;
    229     }
    230 
    231     /**
    232      * Runs a {@link Runnable} in a bg thread, using {@link #PARALLEL_EXECUTOR}.
    233      */
    234     public static EmailAsyncTask<Void, Void, Void> runAsyncParallel(Runnable runnable) {
    235         return runAsyncInternal(PARALLEL_EXECUTOR, runnable);
    236     }
    237 
    238     /**
    239      * Runs a {@link Runnable} in a bg thread, using {@link #SERIAL_EXECUTOR}.
    240      */
    241     public static EmailAsyncTask<Void, Void, Void> runAsyncSerial(Runnable runnable) {
    242         return runAsyncInternal(SERIAL_EXECUTOR, runnable);
    243     }
    244 
    245     private static EmailAsyncTask<Void, Void, Void> runAsyncInternal(Executor executor,
    246             final Runnable runnable) {
    247         EmailAsyncTask<Void, Void, Void> task = new EmailAsyncTask<Void, Void, Void>(null) {
    248             @Override
    249             protected Void doInBackground(Void... params) {
    250                 runnable.run();
    251                 return null;
    252             }
    253         };
    254         return task.executeInternal(executor, false, (Void[]) null);
    255     }
    256 
    257     /**
    258      * Wait until {@link #doInBackground} finishes and returns the results of the computation.
    259      *
    260      * @see AsyncTask#get
    261      */
    262     public final Result get() throws InterruptedException, ExecutionException {
    263         return mInnerTask.get();
    264     }
    265 
    266     /* package */ final Result callDoInBackgroundForTest(Params... params) {
    267         return mInnerTask.doInBackground(params);
    268     }
    269 
    270     /* package */ final void callOnCancelledForTest(Result result) {
    271         mInnerTask.onCancelled(result);
    272     }
    273 
    274     /* package */ final void callOnPostExecuteForTest(Result result) {
    275         mInnerTask.onPostExecute(result);
    276     }
    277 }
    278