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