1293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount/* 2293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Copyright (C) 2014 The Android Open Source Project 3293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 4293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Licensed under the Apache License, Version 2.0 (the "License"); 5293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * you may not use this file except in compliance with the License. 6293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * You may obtain a copy of the License at 7293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 8293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * http://www.apache.org/licenses/LICENSE-2.0 9293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 10293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Unless required by applicable law or agreed to in writing, software 11293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * distributed under the License is distributed on an "AS IS" BASIS, 12293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * See the License for the specific language governing permissions and 14293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * limitations under the License. 15293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 16fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountpackage android.databinding; 17293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 18293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mountimport java.util.ArrayList; 19293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mountimport java.util.List; 20293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 21293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount/** 22c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * A utility for storing and notifying callbacks. This class supports reentrant modification 23293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * of the callbacks during notification without adversely disrupting notifications. 24293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * A common pattern for callbacks is to receive a notification and then remove 25293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * themselves. This class handles this behavior with constant memory under 26293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * most circumstances. 27293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 282c86cdbaf189e2b1774af7f64a2974de9321673fGeorge Mount * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to 29293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * the constructor to define how notifications should be called. That implementation 30c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * does the actual notification on the listener. It is typically a static instance 31c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * that can be reused for all similar CallbackRegistries.</p> 32293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 33c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * <p>This class supports only callbacks with at most three parameters. 34c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * Typically, these are the notification originator and a parameter, with another to 35c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * indicate which method to call, but these may be used as required. If more than 36c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * three parameters are required or primitive types other than the single int provided 37293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * must be used, <code>A</code> should be some kind of containing structure that 38293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * the subclass may reuse between notifications.</p> 39293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 40293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param <C> The callback type. 41293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param <T> The notification sender type. Typically this is the containing class. 425cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param <A> Opaque argument used to pass additional data beyond an int. 43293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 445cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mountpublic class CallbackRegistry<C, T, A> implements Cloneable { 45293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private static final String TAG = "CallbackRegistry"; 46293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 47293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** An ordered collection of listeners waiting to be notified. */ 48293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private List<C> mCallbacks = new ArrayList<C>(); 49293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 50293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 51293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * A bit flag for the first 64 listeners that are removed during notification. 52293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * The lowest significant bit corresponds to the 0th index into mCallbacks. 53293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * For a small number of callbacks, no additional array of objects needs to 54293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * be allocated. 55293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 56293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private long mFirst64Removed = 0x0; 57293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 58293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 59293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Bit flags for the remaining callbacks that are removed during notification. 60293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * When there are more than 64 callbacks and one is marked for removal, a dynamic 61293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * array of bits are allocated for the callbacks. 62293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 63293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private long[] mRemainderRemoved; 64293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 65293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** The recursion level of the notification */ 66293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private int mNotificationLevel; 67293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 68293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** The notification mechanism for notifying an event. */ 695cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount private final NotifierCallback<C, T, A> mNotifier; 70293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 71293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 72293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Creates an EventRegistry that notifies the event with notifier. 73293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param notifier The class to use to notify events. 74293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 755cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount public CallbackRegistry(NotifierCallback<C, T, A> notifier) { 76293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mNotifier = notifier; 77293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 78293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 79293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 80293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Notify all callbacks. 81293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 82293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The originator. This is an opaque parameter passed to 83c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 84293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg An opaque parameter passed to 85c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 865cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque parameter passed to 87c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 88293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 895cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount public synchronized void notifyCallbacks(T sender, int arg, A arg2) { 90293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mNotificationLevel++; 915cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyRecurse(sender, arg, arg2); 92293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mNotificationLevel--; 93293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mNotificationLevel == 0) { 94293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mRemainderRemoved != null) { 95293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = mRemainderRemoved.length - 1; i >= 0; i--) { 96293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long removedBits = mRemainderRemoved[i]; 97293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (removedBits != 0) { 98293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits); 99293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mRemainderRemoved[i] = 0; 100293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 101293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 102293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 103293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mFirst64Removed != 0) { 104293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount removeRemovedCallbacks(0, mFirst64Removed); 105293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mFirst64Removed = 0; 106293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 107293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 108293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 109293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 110293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 111293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>. 112293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 113293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The originator. This is an opaque parameter passed to 114c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 115293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg An opaque parameter passed to 116c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 1175cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque parameter passed to 118c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 119293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 1205cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount private void notifyFirst64(T sender, int arg, A arg2) { 121293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); 1225cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed); 123293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 124293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 125293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 126293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Notify all callbacks using a recursive algorithm to avoid allocating on the heap. 127293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * This part captures the callbacks beyond Long.SIZE that have no bits allocated for 1285cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * removal before it recurses into {@link #notifyRemainder(Object, int, A, int)}. 129293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 130293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * <p>Recursion is used to avoid allocating temporary state on the heap.</p> 131293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 132293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The originator. This is an opaque parameter passed to 133c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 134293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg An opaque parameter passed to 135c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 1365cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque parameter passed to 137c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 138293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 1395cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount private void notifyRecurse(T sender, int arg, A arg2) { 140293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int callbackCount = mCallbacks.size(); 141293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1; 142293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 143293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the 144293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // others. 1455cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyRemainder(sender, arg, arg2, remainderIndex); 146293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 147293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1 148293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // However, we must also keep track of those in mFirst64Removed, so we add 2 instead: 149293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE; 150293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 151293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // The remaining have no bit set 1525cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0); 153293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 154293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 155293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 156293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If 157293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * remainderIndex is -1, the first 64 will be notified instead. 158293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 159293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The originator. This is an opaque parameter passed to 160c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 161293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg An opaque parameter passed to 162c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 1635cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque parameter passed to 164c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 165293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param remainderIndex The index into mRemainderRemoved that should be notified. 166293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 1675cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) { 168293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (remainderIndex < 0) { 1695cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyFirst64(sender, arg, arg2); 170293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 171293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bits = mRemainderRemoved[remainderIndex]; 172293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int startIndex = (remainderIndex + 1) * Long.SIZE; 173293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); 1745cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyRemainder(sender, arg, arg2, remainderIndex - 1); 1755cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits); 176293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 177293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 178293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 179293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 180293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Notify callbacks from startIndex to endIndex, using bits as the bit status 181293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * for whether they have been removed or not. bits should be from mRemainderRemoved or 182293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to 183293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * endIndex should be notified. 184293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 185293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The originator. This is an opaque parameter passed to 186c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 187293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg An opaque parameter passed to 188c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 1895cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque parameter passed to 190c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)} 191293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param startIndex The index into the mCallbacks to start notifying. 192293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param endIndex One past the last index into mCallbacks to notify. 193293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param bits A bit field indicating which callbacks have been removed and shouldn't 194293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * be notified. 195293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 1965cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex, 1975cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount final int endIndex, final long bits) { 198293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount long bitMask = 1; 199293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = startIndex; i < endIndex; i++) { 200293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if ((bits & bitMask) == 0) { 2015cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); 202293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 203293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount bitMask <<= 1; 204293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 205293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 206293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 207293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 208293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Add a callback to be notified. If the callback is already in the list, another won't 209293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * be added. This does not affect current notifications. 210293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param callback The callback to add. 211293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 212293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount public synchronized void add(C callback) { 213293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount int index = mCallbacks.lastIndexOf(callback); 214293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (index < 0 || isRemoved(index)) { 215293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mCallbacks.add(callback); 216293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 217293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 218293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 219293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 220293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Returns true if the callback at index has been marked for removal. 221293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 222293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param index The index into mCallbacks to check. 223293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @return true if the callback at index has been marked for removal. 224293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 225293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private boolean isRemoved(int index) { 226293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (index < Long.SIZE) { 227293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // It is in the first 64 callbacks, just check the bit. 228293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bitMask = 1L << index; 229293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return (mFirst64Removed & bitMask) != 0; 230293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else if (mRemainderRemoved == null) { 231293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // It is after the first 64 callbacks, but nothing else was marked for removal. 232293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return false; 233293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 234293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int maskIndex = (index / Long.SIZE) - 1; 235293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (maskIndex >= mRemainderRemoved.length) { 236293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // There are some items in mRemainderRemoved, but nothing at the given index. 237293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return false; 238293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 239293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // There is something marked for removal, so we have to check the bit. 240293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bits = mRemainderRemoved[maskIndex]; 241293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bitMask = 1L << (index % Long.SIZE); 242293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return (bits & bitMask) != 0; 243293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 244293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 245293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 246293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 247293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 248293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Removes callbacks from startIndex to startIndex + Long.SIZE, based 249293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * on the bits set in removed. 250c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * 251293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param startIndex The index into the mCallbacks to start removing callbacks. 252293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param removed The bits indicating removal, where each bit is set for one callback 253293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * to be removed. 254293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 255293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private void removeRemovedCallbacks(int startIndex, long removed) { 256293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // The naive approach should be fine. There may be a better bit-twiddling approach. 257293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int endIndex = startIndex + Long.SIZE; 258293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 259293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount long bitMask = 1L << (Long.SIZE - 1); 260293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = endIndex - 1; i >= startIndex; i--) { 261293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if ((removed & bitMask) != 0) { 262293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mCallbacks.remove(i); 263293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 264293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount bitMask >>>= 1; 265293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 266293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 267293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 268293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 269293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Remove a callback. This callback won't be notified after this call completes. 270c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * 271293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param callback The callback to remove. 272293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 273293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount public synchronized void remove(C callback) { 274293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mNotificationLevel == 0) { 275293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mCallbacks.remove(callback); 276293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 277293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount int index = mCallbacks.lastIndexOf(callback); 278293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (index >= 0) { 279293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount setRemovalBit(index); 280293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 281293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 282293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 283293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 284293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount private void setRemovalBit(int index) { 285293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (index < Long.SIZE) { 286293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // It is in the first 64 callbacks, just check the bit. 287293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bitMask = 1L << index; 288293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mFirst64Removed |= bitMask; 289293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 290293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int remainderIndex = (index / Long.SIZE) - 1; 291293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mRemainderRemoved == null) { 292293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE]; 293293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else if (mRemainderRemoved.length < remainderIndex) { 294293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount // need to make it bigger 295293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount long[] newRemainders = new long[mCallbacks.size() / Long.SIZE]; 296293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length); 297293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mRemainderRemoved = newRemainders; 298293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 299293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final long bitMask = 1L << (index % Long.SIZE); 300293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mRemainderRemoved[remainderIndex] |= bitMask; 301293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 302293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 303293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 304293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 305293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Makes a copy of the registered callbacks and returns it. 306293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 307293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @return a copy of the registered callbacks. 308293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 309c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount public synchronized ArrayList<C> copyCallbacks() { 310293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size()); 311293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount int numListeners = mCallbacks.size(); 312293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = 0; i < numListeners; i++) { 313293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (!isRemoved(i)) { 314293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount callbacks.add(mCallbacks.get(i)); 315293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 316293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 317293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return callbacks; 318293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 319293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 320293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 321c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * Modifies <code>callbacks</code> to contain all callbacks in the CallbackRegistry. 322c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * 323c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * @param callbacks modified to contain all callbacks registered to receive events. 324c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount */ 325c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount public synchronized void copyCallbacks(List<C> callbacks) { 326c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount callbacks.clear(); 327c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount int numListeners = mCallbacks.size(); 328c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount for (int i = 0; i < numListeners; i++) { 329c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount if (!isRemoved(i)) { 330c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount callbacks.add(mCallbacks.get(i)); 331c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount } 332c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount } 333c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount } 334c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount 335c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount /** 336293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Returns true if there are no registered callbacks or false otherwise. 337293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 338293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @return true if there are no registered callbacks or false otherwise. 339293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 340293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount public synchronized boolean isEmpty() { 341293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mCallbacks.isEmpty()) { 342293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return true; 343293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else if (mNotificationLevel == 0) { 344293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return false; 345293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else { 346293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount int numListeners = mCallbacks.size(); 347293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = 0; i < numListeners; i++) { 348293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (!isRemoved(i)) { 349293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return false; 350293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 351293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 352293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return true; 353293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 354293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 355293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 356293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 357293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Removes all callbacks from the list. 358293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 359293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount public synchronized void clear() { 360293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (mNotificationLevel == 0) { 361293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount mCallbacks.clear(); 362293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } else if (!mCallbacks.isEmpty()) { 363293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = mCallbacks.size() - 1; i >= 0; i--) { 364293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount setRemovalBit(i); 365293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 366293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 367293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 368293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 369c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount /** 370c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * @return A copy of the CallbackRegistry with all callbacks listening to both instances. 371c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount */ 372c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount @SuppressWarnings("unchecked") 3735cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount public synchronized CallbackRegistry<C, T, A> clone() { 3745cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount CallbackRegistry<C, T, A> clone = null; 375293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount try { 3765cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount clone = (CallbackRegistry<C, T, A>) super.clone(); 377293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount clone.mFirst64Removed = 0; 378293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount clone.mRemainderRemoved = null; 379293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount clone.mNotificationLevel = 0; 380293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount clone.mCallbacks = new ArrayList<C>(); 381293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount final int numListeners = mCallbacks.size(); 382293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount for (int i = 0; i < numListeners; i++) { 383293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount if (!isRemoved(i)) { 384293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount clone.mCallbacks.add(mCallbacks.get(i)); 385293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 386293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 387293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } catch (CloneNotSupportedException e) { 3882c86cdbaf189e2b1774af7f64a2974de9321673fGeorge Mount e.printStackTrace(); 389293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 390293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount return clone; 391293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 392293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount 393293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 394293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * Class used to notify events from CallbackRegistry. 395293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 396293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param <C> The callback type. 397293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param <T> The notification sender type. Typically this is the containing class. 3985cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param <A> An opaque argument to pass to the notifier 399293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 4005cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount public abstract static class NotifierCallback<C, T, A> { 401293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount /** 402c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * Called by CallbackRegistry during 403c9a5d6f140f732ca0ff279a4b1ee315072e1c422George Mount * {@link CallbackRegistry#notifyCallbacks(Object, int, Object)}} to notify the callback. 404293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * 405293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param callback The callback to notify. 406293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param sender The opaque sender object. 407293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount * @param arg The opaque notification parameter. 4085cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount * @param arg2 An opaque argument passed in 4092c86cdbaf189e2b1774af7f64a2974de9321673fGeorge Mount * {@link CallbackRegistry#notifyCallbacks} 4102c86cdbaf189e2b1774af7f64a2974de9321673fGeorge Mount * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback) 411293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount */ 4125cd681c345db8f606d7d5a8662e20e059f21a86cGeorge Mount public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); 413293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount } 414293de28642305ce210e1d2a1cfe0abfa4f737d7aGeorge Mount} 415