• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. */
onDrained()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      */
TaskDrainer(Handler handler, DrainListener listener)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      */
TaskDrainer(Handler handler, DrainListener listener, String name)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      */
taskStarted(T task)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      */
taskFinished(T task)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      */
beginDrain()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 
checkIfDrainFinished()195     private void checkIfDrainFinished() {
196         if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
197             mDrainFinished = true;
198             postDrained();
199         }
200     }
201 
postDrained()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