Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2014 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 package android.hardware.camera2.utils;
     17 
     18 import android.os.Handler;
     19 import android.util.Log;
     20 
     21 import java.util.HashSet;
     22 import java.util.Set;
     23 
     24 import static com.android.internal.util.Preconditions.*;
     25 
     26 /**
     27  * Keep track of multiple concurrent tasks starting and finishing by their key;
     28  * allow draining existing tasks and figuring out when all tasks have finished
     29  * (and new ones won't begin).
     30  *
     31  * <p>The initial state is to allow all tasks to be started and finished. A task may only be started
     32  * once, after which it must be finished before starting again. Likewise, a task may only be
     33  * finished once, after which it must be started before finishing again. It is okay to finish a
     34  * task before starting it due to different threads handling starting and finishing.</p>
     35  *
     36  * <p>When draining begins, no more new tasks can be started. This guarantees that at some
     37  * point when all the tasks are finished there will be no more collective new tasks,
     38  * at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
     39  *
     40  *
     41  * @param <T>
     42  *          a type for the key that will represent tracked tasks;
     43  *          must implement {@code Object#equals}
     44  */
     45 public class TaskDrainer<T> {
     46     /**
     47      * Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
     48      * <em>and</em> all tasks that were started have finished.
     49      */
     50     public interface DrainListener {
     51         /** All tasks have fully finished draining; there will be no more pending tasks. */
     52         public void onDrained();
     53     }
     54 
     55     private static final String TAG = "TaskDrainer";
     56     private final boolean DEBUG = false;
     57 
     58     private final Handler mHandler;
     59     private final DrainListener mListener;
     60     private final String mName;
     61 
     62     /** Set of tasks which have been started but not yet finished with #taskFinished */
     63     private final Set<T> mTaskSet = new HashSet<T>();
     64     /**
     65      * Set of tasks which have been finished but not yet started with #taskStarted. This may happen
     66      * if taskStarted and taskFinished are called from two different threads.
     67      */
     68     private final Set<T> mEarlyFinishedTaskSet = new HashSet<T>();
     69     private final Object mLock = new Object();
     70 
     71     private boolean mDraining = false;
     72     private boolean mDrainFinished = false;
     73 
     74     /**
     75      * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
     76      * via the {@code handler}.
     77      *
     78      * @param handler a non-{@code null} handler to use to post runnables to
     79      * @param listener a non-{@code null} listener where {@code onDrained} will be called
     80      */
     81     public TaskDrainer(Handler handler, DrainListener listener) {
     82         mHandler = checkNotNull(handler, "handler must not be null");
     83         mListener = checkNotNull(listener, "listener must not be null");
     84         mName = null;
     85     }
     86 
     87     /**
     88      * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
     89      * via the {@code handler}.
     90      *
     91      * @param handler a non-{@code null} handler to use to post runnables to
     92      * @param listener a non-{@code null} listener where {@code onDrained} will be called
     93      * @param name an optional name used for debug logging
     94      */
     95     public TaskDrainer(Handler handler, DrainListener listener, String name) {
     96         // XX: Probably don't need a handler at all here
     97         mHandler = checkNotNull(handler, "handler must not be null");
     98         mListener = checkNotNull(listener, "listener must not be null");
     99         mName = name;
    100     }
    101 
    102     /**
    103      * Mark an asynchronous task as having started.
    104      *
    105      * <p>A task cannot be started more than once without first having finished. Once
    106      * draining begins with {@link #beginDrain}, no new tasks can be started.</p>
    107      *
    108      * @param task a key to identify a task
    109      *
    110      * @see #taskFinished
    111      * @see #beginDrain
    112      *
    113      * @throws IllegalStateException
    114      *          If attempting to start a task which is already started (and not finished),
    115      *          or if attempting to start a task after draining has begun.
    116      */
    117     public void taskStarted(T task) {
    118         synchronized (mLock) {
    119             if (DEBUG) {
    120                 Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
    121             }
    122 
    123             if (mDraining) {
    124                 throw new IllegalStateException("Can't start more tasks after draining has begun");
    125             }
    126 
    127             // Try to remove the task from the early finished set.
    128             if (!mEarlyFinishedTaskSet.remove(task)) {
    129                 // The task is not finished early. Add it to the started set.
    130                 if (!mTaskSet.add(task)) {
    131                     throw new IllegalStateException("Task " + task + " was already started");
    132                 }
    133             }
    134         }
    135     }
    136 
    137 
    138     /**
    139      * Mark an asynchronous task as having finished.
    140      *
    141      * <p>A task cannot be finished more than once without first having started.</p>
    142      *
    143      * @param task a key to identify a task
    144      *
    145      * @see #taskStarted
    146      * @see #beginDrain
    147      *
    148      * @throws IllegalStateException
    149      *          If attempting to finish a task which is already finished (and not started),
    150      */
    151     public void taskFinished(T task) {
    152         synchronized (mLock) {
    153             if (DEBUG) {
    154                 Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
    155             }
    156 
    157             // Try to remove the task from started set.
    158             if (!mTaskSet.remove(task)) {
    159                 // Task is not started yet. Add it to the early finished set.
    160                 if (!mEarlyFinishedTaskSet.add(task)) {
    161                     throw new IllegalStateException("Task " + task + " was already finished");
    162                 }
    163             }
    164 
    165             // If this is the last finished task and draining has already begun, fire #onDrained
    166             checkIfDrainFinished();
    167         }
    168     }
    169 
    170     /**
    171      * Do not allow any more tasks to be started; once all existing started tasks are finished,
    172      * fire the {@link DrainListener#onDrained} callback asynchronously.
    173      *
    174      * <p>This operation is idempotent; calling it more than once has no effect.</p>
    175      */
    176     public void beginDrain() {
    177         synchronized (mLock) {
    178             if (!mDraining) {
    179                 if (DEBUG) {
    180                     Log.v(TAG + "[" + mName + "]", "beginDrain started");
    181                 }
    182 
    183                 mDraining = true;
    184 
    185                 // If all tasks that had started had already finished by now, fire #onDrained
    186                 checkIfDrainFinished();
    187             } else {
    188                 if (DEBUG) {
    189                     Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
    190                 }
    191             }
    192         }
    193     }
    194 
    195     private void checkIfDrainFinished() {
    196         if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
    197             mDrainFinished = true;
    198             postDrained();
    199         }
    200     }
    201 
    202     private void postDrained() {
    203         mHandler.post(new Runnable() {
    204             @Override
    205             public void run() {
    206                 if (DEBUG) {
    207                     Log.v(TAG + "[" + mName + "]", "onDrained");
    208                 }
    209 
    210                 mListener.onDrained();
    211             }
    212         });
    213     }
    214 }
    215