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