1 /*
2  * Copyright (C) 2015 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 com.android.internal.util;
17 
18 import java.util.ArrayList;
19 import java.util.List;
20 
21 /**
22  * Tracks callbacks for the event. This class supports reentrant modification
23  * of the callbacks during notification without adversely disrupting notifications.
24  * A common pattern for callbacks is to receive a notification and then remove
25  * themselves. This class handles this behavior with constant memory under
26  * most circumstances.
27  *
28  * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to
29  * the constructor to define how notifications should be called. That implementation
30  * does the actual notification on the listener.</p>
31  *
32  * <p>This class supports only callbacks with at most two parameters.
33  * Typically, these are the notification originator and a parameter, but these may
34  * be used as required. If more than two parameters are required or primitive types
35  * must be used, <code>A</code> should be some kind of containing structure that
36  * the subclass may reuse between notifications.</p>
37  *
38  * @param <C> The callback type.
39  * @param <T> The notification sender type. Typically this is the containing class.
40  * @param <A> Opaque argument used to pass additional data beyond an int.
41  */
42 public class CallbackRegistry<C, T, A> implements Cloneable {
43     private static final String TAG = "CallbackRegistry";
44 
45     /** An ordered collection of listeners waiting to be notified. */
46     private List<C> mCallbacks = new ArrayList<C>();
47 
48     /**
49      * A bit flag for the first 64 listeners that are removed during notification.
50      * The lowest significant bit corresponds to the 0th index into mCallbacks.
51      * For a small number of callbacks, no additional array of objects needs to
52      * be allocated.
53      */
54     private long mFirst64Removed = 0x0;
55 
56     /**
57      * Bit flags for the remaining callbacks that are removed during notification.
58      * When there are more than 64 callbacks and one is marked for removal, a dynamic
59      * array of bits are allocated for the callbacks.
60      */
61     private long[] mRemainderRemoved;
62 
63     /**
64      * The reentrancy level of the notification. When we notify a callback, it may cause
65      * further notifications. The reentrancy level must be tracked to let us clean up
66      * the callback state when all notifications have been processed.
67      */
68     private int mNotificationLevel;
69 
70     /** The notification mechanism for notifying an event. */
71     private final NotifierCallback<C, T, A> mNotifier;
72 
73     /**
74      * Creates an EventRegistry that notifies the event with notifier.
75      * @param notifier The class to use to notify events.
76      */
CallbackRegistry(NotifierCallback<C, T, A> notifier)77     public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
78         mNotifier = notifier;
79     }
80 
81     /**
82      * Notify all callbacks.
83      *
84      * @param sender The originator. This is an opaque parameter passed to
85      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
86      * @param arg An opaque parameter passed to
87      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
88      * @param arg2 An opaque parameter passed to
89      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
90      */
notifyCallbacks(T sender, int arg, A arg2)91     public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
92         mNotificationLevel++;
93         notifyRecurseLocked(sender, arg, arg2);
94         mNotificationLevel--;
95         if (mNotificationLevel == 0) {
96             if (mRemainderRemoved != null) {
97                 for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
98                     final long removedBits = mRemainderRemoved[i];
99                     if (removedBits != 0) {
100                         removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
101                         mRemainderRemoved[i] = 0;
102                     }
103                 }
104             }
105             if (mFirst64Removed != 0) {
106                 removeRemovedCallbacks(0, mFirst64Removed);
107                 mFirst64Removed = 0;
108             }
109         }
110     }
111 
112     /**
113      * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>.
114      *
115      * @param sender The originator. This is an opaque parameter passed to
116      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
117      * @param arg An opaque parameter passed to
118      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
119      * @param arg2 An opaque parameter passed to
120      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
121      */
notifyFirst64Locked(T sender, int arg, A arg2)122     private void notifyFirst64Locked(T sender, int arg, A arg2) {
123         final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
124         notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
125     }
126 
127     /**
128      * Notify all callbacks using a recursive algorithm to avoid allocating on the heap.
129      * This part captures the callbacks beyond Long.SIZE that have no bits allocated for
130      * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}.
131      * <p>
132      * Recursion is used to avoid allocating temporary state on the heap. Each stack has one
133      * long (64 callbacks) worth of information of which has been removed.
134      *
135      * @param sender The originator. This is an opaque parameter passed to
136      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
137      * @param arg An opaque parameter passed to
138      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
139      * @param arg2 An opaque parameter passed to
140      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
141      */
notifyRecurseLocked(T sender, int arg, A arg2)142     private void notifyRecurseLocked(T sender, int arg, A arg2) {
143         final int callbackCount = mCallbacks.size();
144         final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
145 
146         // Now we've got all callbacks that have no mRemainderRemoved value, so notify the
147         // others.
148         notifyRemainderLocked(sender, arg, arg2, remainderIndex);
149 
150         // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1
151         // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
152         final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
153 
154         // The remaining have no bit set
155         notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
156     }
157 
158     /**
159      * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If
160      * remainderIndex is -1, the first 64 will be notified instead.
161      *
162      * @param sender The originator. This is an opaque parameter passed to
163      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
164      * @param arg An opaque parameter passed to
165      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
166      * @param arg2 An opaque parameter passed to
167      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
168      * @param remainderIndex The index into mRemainderRemoved that should be notified.
169      */
notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex)170     private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) {
171         if (remainderIndex < 0) {
172             notifyFirst64Locked(sender, arg, arg2);
173         } else {
174             final long bits = mRemainderRemoved[remainderIndex];
175             final int startIndex = (remainderIndex + 1) * Long.SIZE;
176             final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
177             notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1);
178             notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits);
179         }
180     }
181 
182     /**
183      * Notify callbacks from startIndex to endIndex, using bits as the bit status
184      * for whether they have been removed or not. bits should be from mRemainderRemoved or
185      * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to
186      * endIndex should be notified.
187      *
188      * @param sender The originator. This is an opaque parameter passed to
189      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
190      * @param arg An opaque parameter passed to
191      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
192      * @param arg2 An opaque parameter passed to
193      *      {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
194      * @param startIndex The index into the mCallbacks to start notifying.
195      * @param endIndex One past the last index into mCallbacks to notify.
196      * @param bits A bit field indicating which callbacks have been removed and shouldn't
197      *             be notified.
198      */
notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex, final int endIndex, final long bits)199     private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex,
200             final int endIndex, final long bits) {
201         long bitMask = 1;
202         for (int i = startIndex; i < endIndex; i++) {
203             if ((bits & bitMask) == 0) {
204                 mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
205             }
206             bitMask <<= 1;
207         }
208     }
209 
210     /**
211      * Add a callback to be notified. If the callback is already in the list, another won't
212      * be added. This does not affect current notifications.
213      * @param callback The callback to add.
214      */
add(C callback)215     public synchronized void add(C callback) {
216         int index = mCallbacks.lastIndexOf(callback);
217         if (index < 0 || isRemovedLocked(index)) {
218             mCallbacks.add(callback);
219         }
220     }
221 
222     /**
223      * Returns true if the callback at index has been marked for removal.
224      *
225      * @param index The index into mCallbacks to check.
226      * @return true if the callback at index has been marked for removal.
227      */
isRemovedLocked(int index)228     private boolean isRemovedLocked(int index) {
229         if (index < Long.SIZE) {
230             // It is in the first 64 callbacks, just check the bit.
231             final long bitMask = 1L << index;
232             return (mFirst64Removed & bitMask) != 0;
233         } else if (mRemainderRemoved == null) {
234             // It is after the first 64 callbacks, but nothing else was marked for removal.
235             return false;
236         } else {
237             final int maskIndex = (index / Long.SIZE) - 1;
238             if (maskIndex >= mRemainderRemoved.length) {
239                 // There are some items in mRemainderRemoved, but nothing at the given index.
240                 return false;
241             } else {
242                 // There is something marked for removal, so we have to check the bit.
243                 final long bits = mRemainderRemoved[maskIndex];
244                 final long bitMask = 1L << (index % Long.SIZE);
245                 return (bits & bitMask) != 0;
246             }
247         }
248     }
249 
250     /**
251      * Removes callbacks from startIndex to startIndex + Long.SIZE, based
252      * on the bits set in removed.
253      * @param startIndex The index into the mCallbacks to start removing callbacks.
254      * @param removed The bits indicating removal, where each bit is set for one callback
255      *                to be removed.
256      */
removeRemovedCallbacks(int startIndex, long removed)257     private void removeRemovedCallbacks(int startIndex, long removed) {
258         // The naive approach should be fine. There may be a better bit-twiddling approach.
259         final int endIndex = startIndex + Long.SIZE;
260 
261         long bitMask = 1L << (Long.SIZE - 1);
262         for (int i = endIndex - 1; i >= startIndex; i--) {
263             if ((removed & bitMask) != 0) {
264                 mCallbacks.remove(i);
265             }
266             bitMask >>>= 1;
267         }
268     }
269 
270     /**
271      * Remove a callback. This callback won't be notified after this call completes.
272      * @param callback The callback to remove.
273      */
remove(C callback)274     public synchronized void remove(C callback) {
275         if (mNotificationLevel == 0) {
276             mCallbacks.remove(callback);
277         } else {
278             int index = mCallbacks.lastIndexOf(callback);
279             if (index >= 0) {
280                 setRemovalBitLocked(index);
281             }
282         }
283     }
284 
setRemovalBitLocked(int index)285     private void setRemovalBitLocked(int index) {
286         if (index < Long.SIZE) {
287             // It is in the first 64 callbacks, just check the bit.
288             final long bitMask = 1L << index;
289             mFirst64Removed |= bitMask;
290         } else {
291             final int remainderIndex = (index / Long.SIZE) - 1;
292             if (mRemainderRemoved == null) {
293                 mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
294             } else if (mRemainderRemoved.length < remainderIndex) {
295                 // need to make it bigger
296                 long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
297                 System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
298                 mRemainderRemoved = newRemainders;
299             }
300             final long bitMask = 1L << (index % Long.SIZE);
301             mRemainderRemoved[remainderIndex] |= bitMask;
302         }
303     }
304 
305     /**
306      * Makes a copy of the registered callbacks and returns it.
307      *
308      * @return a copy of the registered callbacks.
309      */
copyListeners()310     public synchronized ArrayList<C> copyListeners() {
311         ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
312         int numListeners = mCallbacks.size();
313         for (int i = 0; i < numListeners; i++) {
314             if (!isRemovedLocked(i)) {
315                 callbacks.add(mCallbacks.get(i));
316             }
317         }
318         return callbacks;
319     }
320 
321     /**
322      * Returns true if there are no registered callbacks or false otherwise.
323      *
324      * @return true if there are no registered callbacks or false otherwise.
325      */
isEmpty()326     public synchronized boolean isEmpty() {
327         if (mCallbacks.isEmpty()) {
328             return true;
329         } else if (mNotificationLevel == 0) {
330             return false;
331         } else {
332             int numListeners = mCallbacks.size();
333             for (int i = 0; i < numListeners; i++) {
334                 if (!isRemovedLocked(i)) {
335                     return false;
336                 }
337             }
338             return true;
339         }
340     }
341 
342     /**
343      * Removes all callbacks from the list.
344      */
clear()345     public synchronized void clear() {
346         if (mNotificationLevel == 0) {
347             mCallbacks.clear();
348         } else if (!mCallbacks.isEmpty()) {
349             for (int i = mCallbacks.size() - 1; i >= 0; i--) {
350                 setRemovalBitLocked(i);
351             }
352         }
353     }
354 
clone()355     public synchronized CallbackRegistry<C, T, A> clone() {
356         CallbackRegistry<C, T, A> clone = null;
357         try {
358             clone = (CallbackRegistry<C, T, A>) super.clone();
359             clone.mFirst64Removed = 0;
360             clone.mRemainderRemoved = null;
361             clone.mNotificationLevel = 0;
362             clone.mCallbacks = new ArrayList<C>();
363             final int numListeners = mCallbacks.size();
364             for (int i = 0; i < numListeners; i++) {
365                 if (!isRemovedLocked(i)) {
366                     clone.mCallbacks.add(mCallbacks.get(i));
367                 }
368             }
369         } catch (CloneNotSupportedException e) {
370             e.printStackTrace();
371         }
372         return clone;
373     }
374 
375     /**
376      * Class used to notify events from CallbackRegistry.
377      *
378      * @param <C> The callback type.
379      * @param <T> The notification sender type. Typically this is the containing class.
380      * @param <A> An opaque argument to pass to the notifier
381      */
382     public abstract static class NotifierCallback<C, T, A> {
383         /**
384          * Used to notify the callback.
385          *
386          * @param callback The callback to notify.
387          * @param sender The opaque sender object.
388          * @param arg The opaque notification parameter.
389          * @param arg2 An opaque argument passed in
390          *        {@link CallbackRegistry#notifyCallbacks}
391          * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
392          */
onNotifyCallback(C callback, T sender, int arg, A arg2)393         public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
394     }
395 }
396