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