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 */ 16package android.hardware.camera2.utils; 17 18import android.os.Handler; 19import android.util.Log; 20 21import java.util.HashSet; 22import java.util.Set; 23 24import 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 */ 44public 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. */ 51 public void onDrained(); 52 } 53 54 private static final String TAG = "TaskDrainer"; 55 private final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 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 */ 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 */ 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 */ 111 public void taskStarted(T task) { 112 synchronized (mLock) { 113 if (VERBOSE) { 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 */ 142 public void taskFinished(T task) { 143 synchronized (mLock) { 144 if (VERBOSE) { 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 */ 163 public void beginDrain() { 164 synchronized (mLock) { 165 if (!mDraining) { 166 if (VERBOSE) { 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 (VERBOSE) { 176 Log.v(TAG + "[" + mName + "]", "beginDrain ignored"); 177 } 178 } 179 } 180 } 181 182 private void checkIfDrainFinished() { 183 if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) { 184 mDrainFinished = true; 185 postDrained(); 186 } 187 } 188 189 private void postDrained() { 190 mHandler.post(new Runnable() { 191 @Override 192 public void run() { 193 if (VERBOSE) { 194 Log.v(TAG + "[" + mName + "]", "onDrained"); 195 } 196 197 mListener.onDrained(); 198 } 199 }); 200 } 201} 202