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 */
16package com.android.internal.util;
17
18import java.util.ArrayList;
19import 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 */
42public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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
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         */
393        public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
394    }
395}
396