Remove ViewTreeObserver allocations

A couple of allocations remain to handle possibly recursive listeners.

Change-Id: I72fd271c2fc4f4ad427a27e0665f780cae117aea
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 1c5d436..cfcf3c0 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -32,12 +32,18 @@
  * for more information.
  */
 public final class ViewTreeObserver {
+    // Recursive listeners use CopyOnWriteArrayList
     private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
-    private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
     private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
-    private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
-    private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
-    private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
+
+    // Non-recursive listeners use CopyOnWriteArray
+    // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
+    private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+    private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+    private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
+    private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+
+    // These listeners cannot be mutated during dispatch
     private ArrayList<OnDrawListener> mOnDrawListeners;
 
     private boolean mAlive = true;
@@ -147,7 +153,7 @@
          * windows behind it should be placed.
          */
         public final Rect contentInsets = new Rect();
-        
+
         /**
          * Offsets from the frame of the window at which windows behind it
          * are visible.
@@ -166,13 +172,13 @@
          * can be touched.
          */
         public static final int TOUCHABLE_INSETS_FRAME = 0;
-        
+
         /**
          * Option for {@link #setTouchableInsets(int)}: the area inside of
          * the content insets can be touched.
          */
         public static final int TOUCHABLE_INSETS_CONTENT = 1;
-        
+
         /**
          * Option for {@link #setTouchableInsets(int)}: the area inside of
          * the visible insets can be touched.
@@ -195,7 +201,7 @@
         }
 
         int mTouchableInsets;
-        
+
         void reset() {
             contentInsets.setEmpty();
             visibleInsets.setEmpty();
@@ -231,7 +237,7 @@
             mTouchableInsets = other.mTouchableInsets;
         }
     }
-    
+
     /**
      * Interface definition for a callback to be invoked when layout has
      * completed and the client can compute its interior insets.
@@ -363,7 +369,7 @@
         checkIsAlive();
 
         if (mOnGlobalLayoutListeners == null) {
-            mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>();
+            mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
         }
 
         mOnGlobalLayoutListeners.add(listener);
@@ -413,7 +419,7 @@
         checkIsAlive();
 
         if (mOnPreDrawListeners == null) {
-            mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
+            mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
         }
 
         mOnPreDrawListeners.add(listener);
@@ -485,7 +491,7 @@
         checkIsAlive();
 
         if (mOnScrollChangedListeners == null) {
-            mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>();
+            mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
         }
 
         mOnScrollChangedListeners.add(listener);
@@ -558,7 +564,7 @@
 
         if (mOnComputeInternalInsetsListeners == null) {
             mOnComputeInternalInsetsListeners =
-                    new CopyOnWriteArrayList<OnComputeInternalInsetsListener>();
+                    new CopyOnWriteArray<OnComputeInternalInsetsListener>();
         }
 
         mOnComputeInternalInsetsListeners.add(listener);
@@ -640,10 +646,16 @@
         // perform the dispatching. The iterator is a safe guard against listeners that
         // could mutate the list by calling the various add/remove methods. This prevents
         // the array from being modified while we iterate it.
-        final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
+        final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
         if (listeners != null && listeners.size() > 0) {
-            for (OnGlobalLayoutListener listener : listeners) {
-                listener.onGlobalLayout();
+            CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onGlobalLayout();
+                }
+            } finally {
+                listeners.end();
             }
         }
     }
@@ -658,17 +670,17 @@
      */
     @SuppressWarnings("unchecked")
     public final boolean dispatchOnPreDraw() {
-        // NOTE: we *must* clone the listener list to perform the dispatching.
-        // The clone is a safe guard against listeners that
-        // could mutate the list by calling the various add/remove methods. This prevents
-        // the array from being modified while we process it.
         boolean cancelDraw = false;
-        if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) {
-            final ArrayList<OnPreDrawListener> listeners =
-                    (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone();
-            int numListeners = listeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                cancelDraw |= !(listeners.get(i).onPreDraw());
+        final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
+        if (listeners != null && listeners.size() > 0) {
+            CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    cancelDraw |= !(access.get(i).onPreDraw());
+                }
+            } finally {
+                listeners.end();
             }
         }
         return cancelDraw;
@@ -710,10 +722,16 @@
         // perform the dispatching. The iterator is a safe guard against listeners that
         // could mutate the list by calling the various add/remove methods. This prevents
         // the array from being modified while we iterate it.
-        final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+        final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
         if (listeners != null && listeners.size() > 0) {
-            for (OnScrollChangedListener listener : listeners) {
-                listener.onScrollChanged();
+            CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onScrollChanged();
+                }
+            } finally {
+                listeners.end();
             }
         }
     }
@@ -722,11 +740,11 @@
      * Returns whether there are listeners for computing internal insets.
      */
     final boolean hasComputeInternalInsetsListeners() {
-        final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
+        final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
                 mOnComputeInternalInsetsListeners;
         return (listeners != null && listeners.size() > 0);
     }
-    
+
     /**
      * Calls all listeners to compute the current insets.
      */
@@ -735,12 +753,105 @@
         // perform the dispatching. The iterator is a safe guard against listeners that
         // could mutate the list by calling the various add/remove methods. This prevents
         // the array from being modified while we iterate it.
-        final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
+        final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
                 mOnComputeInternalInsetsListeners;
         if (listeners != null && listeners.size() > 0) {
-            for (OnComputeInternalInsetsListener listener : listeners) {
-                listener.onComputeInternalInsets(inoutInfo);
+            CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
+            try {
+                int count = access.size();
+                for (int i = 0; i < count; i++) {
+                    access.get(i).onComputeInternalInsets(inoutInfo);
+                }
+            } finally {
+                listeners.end();
             }
         }
     }
+
+    /**
+     * Copy on write array. This array is not thread safe, and only one loop can
+     * iterate over this array at any given time. This class avoids allocations
+     * until a concurrent modification happens.
+     * 
+     * Usage:
+     * 
+     * CopyOnWriteArray.Access<MyData> access = array.start();
+     * try {
+     *     for (int i = 0; i < access.size(); i++) {
+     *         MyData d = access.get(i);
+     *     }
+     * } finally {
+     *     access.end();
+     * }
+     */
+    static class CopyOnWriteArray<T> {
+        private ArrayList<T> mData = new ArrayList<T>();
+        private ArrayList<T> mDataCopy;
+
+        private final Access<T> mAccess = new Access<T>();
+
+        private boolean mStart;
+
+        static class Access<T> {
+            private ArrayList<T> mData;
+            private int mSize;
+
+            T get(int index) {
+                return mData.get(index);
+            }
+
+            int size() {
+                return mSize;
+            }
+        }
+
+        CopyOnWriteArray() {
+        }
+
+        private ArrayList<T> getArray() {
+            if (mStart) {
+                if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
+                return mDataCopy;
+            }
+            return mData;
+        }
+
+        Access<T> start() {
+            if (mStart) throw new IllegalStateException("Iteration already started");
+            mStart = true;
+            mDataCopy = null;
+            mAccess.mData = mData;
+            mAccess.mSize = mData.size();
+            return mAccess;
+        }
+
+        void end() {
+            if (!mStart) throw new IllegalStateException("Iteration not started");
+            mStart = false;
+            if (mDataCopy != null) {
+                mData = mDataCopy;
+            }
+            mDataCopy = null;
+        }
+
+        int size() {
+            return getArray().size();
+        }
+
+        void add(T item) {
+            getArray().add(item);
+        }
+
+        void addAll(CopyOnWriteArray<T> array) {
+            getArray().addAll(array.mData);
+        }
+
+        void remove(T item) {
+            getArray().remove(item);
+        }
+
+        void clear() {
+            getArray().clear();
+        }
+    }
 }