Merge "Use safe access to OnPreDrawListener." into nyc-support-25.1-dev
diff --git a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
index a8323dc..879be4a 100644
--- a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -24,7 +24,6 @@
 import android.transition.TransitionSet;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -343,18 +342,15 @@
             }
         }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        for (int i = 0; i < numSharedElements; i++) {
-                            sharedElementsIn.get(i).setTransitionName(inNames.get(i));
-                            sharedElementsOut.get(i).setTransitionName(outNames.get(i));
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < numSharedElements; i++) {
+                    sharedElementsIn.get(i).setTransitionName(inNames.get(i));
+                    sharedElementsOut.get(i).setTransitionName(outNames.get(i));
+                }
+            }
+        });
     }
 
     /**
@@ -406,23 +402,20 @@
 
     public static void setNameOverridesUnoptimized(final View sceneRoot,
             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        final int numSharedElements = sharedElementsIn.size();
-                        for (int i = 0; i < numSharedElements; i++) {
-                            View view = sharedElementsIn.get(i);
-                            String name = view.getTransitionName();
-                            if (name != null) {
-                                String inName = findKeyForValue(nameOverrides, name);
-                                view.setTransitionName(inName);
-                            }
-                        }
-                        return true;
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                final int numSharedElements = sharedElementsIn.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    View view = sharedElementsIn.get(i);
+                    String name = view.getTransitionName();
+                    if (name != null) {
+                        String inName = findKeyForValue(nameOverrides, name);
+                        view.setTransitionName(inName);
                     }
-                });
+                }
+            }
+        });
     }
 
     /**
@@ -566,20 +559,17 @@
 
     public static void scheduleNameReset(final ViewGroup sceneRoot,
             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        final int numSharedElements = sharedElementsIn.size();
-                        for (int i = 0; i < numSharedElements; i++) {
-                            final View view = sharedElementsIn.get(i);
-                            final String name = view.getTransitionName();
-                            final String inName = nameOverrides.get(name);
-                            view.setTransitionName(inName);
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                final int numSharedElements = sharedElementsIn.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    final View view = sharedElementsIn.get(i);
+                    final String name = view.getTransitionName();
+                    final String inName = nameOverrides.get(name);
+                    view.setTransitionName(inName);
+                }
+            }
+        });
     }
 }
diff --git a/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
new file mode 100644
index 0000000..502af1f
--- /dev/null
+++ b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * An OnPreDrawListener that will remove itself after one OnPreDraw call. Typical
+ * usage is:
+ * <pre><code>
+ *     OneShotPreDrawListener.add(view, () -> { view.doSomething(); })
+ * </code></pre>
+ * <p>
+ * The onPreDraw always returns true.
+ * <p>
+ * The listener will also remove itself from the ViewTreeObserver when the view
+ * is detached from the view hierarchy. In that case, the Runnable will never be
+ * executed.
+ */
+class OneShotPreDrawListener implements ViewTreeObserver.OnPreDrawListener,
+        View.OnAttachStateChangeListener {
+    private final View mView;
+    private ViewTreeObserver mViewTreeObserver;
+    private final Runnable mRunnable;
+
+    private OneShotPreDrawListener(View view, Runnable runnable) {
+        mView = view;
+        mViewTreeObserver = view.getViewTreeObserver();
+        mRunnable = runnable;
+    }
+
+    /**
+     * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver.
+     * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
+     * @param runnable The Runnable to execute in the OnPreDraw (once)
+     * @return The added OneShotPreDrawListener. It can be removed prior to
+     * the onPreDraw by calling {@link #removeListener()}.
+     */
+    public static OneShotPreDrawListener add(View view, Runnable runnable) {
+        OneShotPreDrawListener listener = new OneShotPreDrawListener(view, runnable);
+        view.getViewTreeObserver().addOnPreDrawListener(listener);
+        view.addOnAttachStateChangeListener(listener);
+        return listener;
+    }
+
+    @Override
+    public boolean onPreDraw() {
+        removeListener();
+        mRunnable.run();
+        return true;
+    }
+
+    /**
+     * Removes the listener from the ViewTreeObserver. This is useful to call if the
+     * callback should be removed prior to {@link #onPreDraw()}.
+     */
+    public void removeListener() {
+        if (mViewTreeObserver.isAlive()) {
+            mViewTreeObserver.removeOnPreDrawListener(this);
+        } else {
+            mView.getViewTreeObserver().removeOnPreDrawListener(this);
+        }
+        mView.removeOnAttachStateChangeListener(this);
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View v) {
+        mViewTreeObserver = v.getViewTreeObserver();
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View v) {
+        removeListener();
+    }
+}
diff --git a/fragment/java/android/support/v4/app/FragmentTransition.java b/fragment/java/android/support/v4/app/FragmentTransition.java
index 9efd1bc..4676434 100644
--- a/fragment/java/android/support/v4/app/FragmentTransition.java
+++ b/fragment/java/android/support/v4/app/FragmentTransition.java
@@ -22,7 +22,6 @@
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -250,15 +249,12 @@
             FragmentTransitionCompat21.scheduleHideFragmentView(exitTransition,
                     exitingFragment.getView(), exitingViews);
             final ViewGroup container = exitingFragment.mContainer;
-            container.getViewTreeObserver().addOnPreDrawListener(
-                    new ViewTreeObserver.OnPreDrawListener() {
-                        @Override
-                        public boolean onPreDraw() {
-                            container.getViewTreeObserver().removeOnPreDrawListener(this);
-                            setViewVisibility(exitingViews, View.INVISIBLE);
-                            return true;
-                        }
-                    });
+            OneShotPreDrawListener.add(container, new Runnable() {
+                @Override
+                public void run() {
+                    setViewVisibility(exitingViews, View.INVISIBLE);
+                }
+            });
         }
     }
 
@@ -356,33 +352,27 @@
             final ArrayList<View> sharedElementsIn,
             final Object enterTransition, final ArrayList<View> enteringViews,
             final Object exitTransition, final ArrayList<View> exitingViews) {
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                if (enterTransition != null) {
+                    FragmentTransitionCompat21.removeTarget(enterTransition,
+                            nonExistentView);
+                    ArrayList<View> views = configureEnteringExitingViews(
+                            enterTransition, inFragment, sharedElementsIn, nonExistentView);
+                    enteringViews.addAll(views);
+                }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-
-                        if (enterTransition != null) {
-                            FragmentTransitionCompat21.removeTarget(enterTransition,
-                                    nonExistentView);
-                            ArrayList<View> views = configureEnteringExitingViews(
-                                    enterTransition, inFragment, sharedElementsIn, nonExistentView);
-                            enteringViews.addAll(views);
-                        }
-
-                        if (exitingViews != null) {
-                            ArrayList<View> tempExiting = new ArrayList<>();
-                            tempExiting.add(nonExistentView);
-                            FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
-                                    tempExiting);
-                            exitingViews.clear();
-                            exitingViews.add(nonExistentView);
-                        }
-
-                        return true;
-                    }
-                });
+                if (exitingViews != null) {
+                    ArrayList<View> tempExiting = new ArrayList<>();
+                    tempExiting.add(nonExistentView);
+                    FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
+                            tempExiting);
+                    exitingViews.clear();
+                    exitingViews.add(nonExistentView);
+                }
+            }
+        });
     }
 
     /**
@@ -519,19 +509,16 @@
             epicenterView = null;
         }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
-                                inSharedElements, false);
-                        if (epicenterView != null) {
-                            FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                        inSharedElements, false);
+                if (epicenterView != null) {
+                    FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
+                }
+            }
+        });
         return sharedElementTransition;
     }
 
@@ -612,38 +599,36 @@
             inEpicenter = null;
         }
 
+
         final Object finalSharedElementTransition = sharedElementTransition;
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                ArrayMap<String, View> inSharedElements = captureInSharedElements(
+                        nameOverrides, finalSharedElementTransition, fragments);
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        ArrayMap<String, View> inSharedElements = captureInSharedElements(
-                                nameOverrides, finalSharedElementTransition, fragments);
+                if (inSharedElements != null) {
+                    sharedElementsIn.addAll(inSharedElements.values());
+                    sharedElementsIn.add(nonExistentView);
+                }
 
-                        if (inSharedElements != null) {
-                            sharedElementsIn.addAll(inSharedElements.values());
-                            sharedElementsIn.add(nonExistentView);
-                        }
+                callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                        inSharedElements, false);
+                if (finalSharedElementTransition != null) {
+                    FragmentTransitionCompat21.swapSharedElementTargets(
+                            finalSharedElementTransition, sharedElementsOut,
+                            sharedElementsIn);
 
-                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
-                                inSharedElements, false);
-                        if (finalSharedElementTransition != null) {
-                            FragmentTransitionCompat21.swapSharedElementTargets(
-                                    finalSharedElementTransition, sharedElementsOut,
-                                    sharedElementsIn);
-
-                            final View inEpicenterView = getInEpicenterView(inSharedElements,
-                                    fragments, enterTransition, inIsPop);
-                            if (inEpicenterView != null) {
-                                FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
-                                        inEpicenter);
-                            }
-                        }
-                        return true;
+                    final View inEpicenterView = getInEpicenterView(inSharedElements,
+                            fragments, enterTransition, inIsPop);
+                    if (inEpicenterView != null) {
+                        FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
+                                inEpicenter);
                     }
-                });
+                }
+            }
+        });
+
         return sharedElementTransition;
     }