Use safe access to OnPreDrawListener.

Bug 32472451

It is important to remove an OnPreDrawListener from the correct
ViewTreeObserver. When a View is added to the view hierarchy, the
initial ViewTreeObserver is not active. The listener must then be
removed from the current OnPreDrawListener. When a View has been
removed from the hierarchy, it is important to remove the listener
from the orignal ViewTreeObserver.

Test: cts-tradefed run singleCommand cts -d --skip-preconditions
--skip-connectivity-check -m CtsUsageStatsTestCases
Test: cts-tradefed run singleCommand cts -d --skip-preconditions
--skip-connectivity-check -m CtsFragmentTestCases

Change-Id: I735f71d2d9c84e86ef846aab0088a8651300fbe8
This commit is contained in:
George Mount
2016-10-31 14:04:13 -07:00
parent 5ddd7172b1
commit f0b46b9540
7 changed files with 218 additions and 163 deletions

View File

@@ -37,6 +37,8 @@ import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
import com.android.internal.view.OneShotPreDrawListener;
import java.util.ArrayList;
import java.util.Collection;
@@ -570,16 +572,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
final View decorView = getDecor();
if (decorView != null) {
decorView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
notifySharedElementEnd(snapshots);
return true;
}
}
);
OneShotPreDrawListener.add(decorView, () -> {
notifySharedElementEnd(snapshots);
});
}
}
@@ -816,6 +811,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
if (moveWithParent && !isInTransitionGroup(parent, decor)) {
GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
parent.getViewTreeObserver().addOnPreDrawListener(listener);
parent.addOnAttachStateChangeListener(listener);
mGhostViewListeners.add(listener);
}
}
@@ -842,8 +838,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
int numListeners = mGhostViewListeners.size();
for (int i = 0; i < numListeners; i++) {
GhostViewListeners listener = mGhostViewListeners.get(i);
ViewGroup parent = (ViewGroup) listener.getView().getParent();
parent.getViewTreeObserver().removeOnPreDrawListener(listener);
listener.removeListener();
}
mGhostViewListeners.clear();
@@ -874,15 +869,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
protected void scheduleGhostVisibilityChange(final int visibility) {
final View decorView = getDecor();
if (decorView != null) {
decorView.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
setGhostVisibility(visibility);
return true;
}
});
OneShotPreDrawListener.add(decorView, () -> {
setGhostVisibility(visibility);
});
}
}
@@ -988,16 +977,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
}
}
private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener {
private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener,
View.OnAttachStateChangeListener {
private View mView;
private ViewGroup mDecor;
private View mParent;
private Matrix mMatrix = new Matrix();
private ViewTreeObserver mViewTreeObserver;
public GhostViewListeners(View view, View parent, ViewGroup decor) {
mView = view;
mParent = parent;
mDecor = decor;
mViewTreeObserver = parent.getViewTreeObserver();
}
public View getView() {
@@ -1008,13 +1000,32 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
public boolean onPreDraw() {
GhostView ghostView = GhostView.getGhost(mView);
if (ghostView == null) {
mParent.getViewTreeObserver().removeOnPreDrawListener(this);
removeListener();
} else {
GhostView.calculateMatrix(mView, mDecor, mMatrix);
ghostView.setMatrix(mMatrix);
}
return true;
}
public void removeListener() {
if (mViewTreeObserver.isAlive()) {
mViewTreeObserver.removeOnPreDrawListener(this);
} else {
mParent.getViewTreeObserver().removeOnPreDrawListener(this);
}
mParent.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewAttachedToWindow(View v) {
mViewTreeObserver = v.getViewTreeObserver();
}
@Override
public void onViewDetachedFromWindow(View v) {
removeListener();
}
}
static class SharedElementOriginalState {

View File

@@ -22,9 +22,10 @@ import android.transition.Transition;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import com.android.internal.view.OneShotPreDrawListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -321,18 +322,12 @@ class ActivityTransitionState {
}
if (delayExitBack && decor != null) {
final ViewGroup finalDecor = decor;
decor.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
finalDecor.getViewTreeObserver().removeOnPreDrawListener(this);
if (mReturnExitCoordinator != null) {
mReturnExitCoordinator.startExit(activity.mResultCode,
activity.mResultData);
}
return true;
}
});
OneShotPreDrawListener.add(decor, () -> {
if (mReturnExitCoordinator != null) {
mReturnExitCoordinator.startExit(activity.mResultCode,
activity.mResultData);
}
});
} else {
mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
}

View File

@@ -30,10 +30,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.view.OneShotPreDrawListener;
import java.util.ArrayList;
/**
@@ -58,7 +59,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
private boolean mAreViewsReady;
private boolean mIsViewsTransitionStarted;
private Transition mEnterViewsTransition;
private OnPreDrawListener mViewsReadyListener;
private OneShotPreDrawListener mViewsReadyListener;
private final boolean mIsCrossTask;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
@@ -74,12 +75,17 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
final View decorView = getDecor();
if (decorView != null) {
decorView.getViewTreeObserver().addOnPreDrawListener(
final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (mIsReadyForTransition) {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeOnPreDrawListener(this);
} else {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
return mIsReadyForTransition;
}
@@ -147,16 +153,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
(sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
viewsReady(sharedElements);
} else {
mViewsReadyListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mViewsReadyListener = null;
decor.getViewTreeObserver().removeOnPreDrawListener(this);
viewsReady(sharedElements);
return true;
}
};
decor.getViewTreeObserver().addOnPreDrawListener(mViewsReadyListener);
mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> {
mViewsReadyListener = null;
viewsReady(sharedElements);
});
decor.invalidate();
}
}
@@ -206,19 +206,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
} else if (decorView != null) {
decorView.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
if (mResultReceiver != null) {
Bundle state = captureSharedElementState();
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
}
return true;
}
});
OneShotPreDrawListener.add(decorView, () -> {
if (mResultReceiver != null) {
Bundle state = captureSharedElementState();
moveSharedElementsToOverlay();
mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
}
});
}
if (allowOverlappingTransitions()) {
startEnterTransitionOnly();
@@ -271,7 +265,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
mIsReadyForTransition = true;
final ViewGroup decor = getDecor();
if (decor != null && mViewsReadyListener != null) {
decor.getViewTreeObserver().removeOnPreDrawListener(mViewsReadyListener);
mViewsReadyListener.removeListener();
mViewsReadyListener = null;
}
showViews(mTransitioningViews, true);
@@ -457,20 +451,11 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
public void onSharedElementsReady() {
final View decorView = getDecor();
if (decorView != null) {
decorView.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
startTransition(new Runnable() {
@Override
public void run() {
startSharedElementTransition(sharedElementState);
}
});
return false;
}
});
OneShotPreDrawListener.add(decorView, () -> {
startTransition(() -> {
startSharedElementTransition(sharedElementState);
});
});
decorView.invalidate();
}
}

View File

@@ -34,9 +34,10 @@ import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import com.android.internal.view.OneShotPreDrawListener;
import java.util.ArrayList;
/**
@@ -168,15 +169,9 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
});
final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
mSharedElementNames);
decorView.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decorView.getViewTreeObserver().removeOnPreDrawListener(this);
setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
return true;
}
});
OneShotPreDrawListener.add(decorView, () -> {
setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
});
setGhostVisibility(View.INVISIBLE);
scheduleGhostVisibilityChange(View.INVISIBLE);
if (mListener != null) {

View File

@@ -24,7 +24,8 @@ import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import com.android.internal.view.OneShotPreDrawListener;
import java.util.ArrayList;
import java.util.Collection;
@@ -320,16 +321,9 @@ class FragmentTransition {
&& exitingFragment.mHidden && exitingFragment.mHiddenChanged) {
exitingFragment.setHideReplaced(true);
final View fragmentView = exitingFragment.getView();
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(exitingFragment.mContainer, () -> {
setViewVisibility(exitingViews, View.INVISIBLE);
});
exitTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
@@ -364,30 +358,22 @@ class FragmentTransition {
final Transition enterTransition, final ArrayList<View> enteringViews,
final Transition exitTransition, final ArrayList<View> exitingViews) {
sceneRoot.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
OneShotPreDrawListener.add(sceneRoot, () -> {
if (enterTransition != null) {
enterTransition.removeTarget(nonExistentView);
ArrayList<View> views = configureEnteringExitingViews(
enterTransition, inFragment, sharedElementsIn, nonExistentView);
enteringViews.addAll(views);
}
if (enterTransition != null) {
enterTransition.removeTarget(nonExistentView);
ArrayList<View> views = configureEnteringExitingViews(
enterTransition, inFragment, sharedElementsIn, nonExistentView);
enteringViews.addAll(views);
}
if (exitingViews != null) {
ArrayList<View> tempExiting = new ArrayList<>();
tempExiting.add(nonExistentView);
replaceTargets(exitTransition, exitingViews, tempExiting);
exitingViews.clear();
exitingViews.add(nonExistentView);
}
return true;
}
});
if (exitingViews != null) {
ArrayList<View> tempExiting = new ArrayList<>();
tempExiting.add(nonExistentView);
replaceTargets(exitTransition, exitingViews, tempExiting);
exitingViews.clear();
exitingViews.add(nonExistentView);
}
});
}
/**
@@ -541,19 +527,13 @@ class FragmentTransition {
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) {
epicenterView.getBoundsOnScreen(epicenter);
}
return true;
}
});
OneShotPreDrawListener.add(sceneRoot, () -> {
callSharedElementStartEnd(inFragment, outFragment, inIsPop,
inSharedElements, false);
if (epicenterView != null) {
epicenterView.getBoundsOnScreen(epicenter);
}
});
return sharedElementTransition;
}
@@ -643,36 +623,30 @@ class FragmentTransition {
TransitionSet finalSharedElementTransition = sharedElementTransition;
sceneRoot.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
ArrayMap<String, View> inSharedElements = captureInSharedElements(
nameOverrides, finalSharedElementTransition, fragments);
OneShotPreDrawListener.add(sceneRoot, () -> {
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) {
finalSharedElementTransition.getTargets().clear();
finalSharedElementTransition.getTargets().addAll(sharedElementsIn);
replaceTargets(finalSharedElementTransition, sharedElementsOut,
sharedElementsIn);
callSharedElementStartEnd(inFragment, outFragment, inIsPop,
inSharedElements, false);
if (finalSharedElementTransition != null) {
finalSharedElementTransition.getTargets().clear();
finalSharedElementTransition.getTargets().addAll(sharedElementsIn);
replaceTargets(finalSharedElementTransition, sharedElementsOut,
sharedElementsIn);
final View inEpicenterView = getInEpicenterView(inSharedElements,
fragments, enterTransition, inIsPop);
if (inEpicenterView != null) {
inEpicenterView.getBoundsOnScreen(inEpicenter);
}
}
return true;
}
});
final View inEpicenterView = getInEpicenterView(inSharedElements,
fragments, enterTransition, inIsPop);
if (inEpicenterView != null) {
inEpicenterView.getBoundsOnScreen(inEpicenter);
}
}
});
return sharedElementTransition;
}

View File

@@ -242,14 +242,20 @@ public class TransitionManager {
Transition mTransition;
ViewGroup mSceneRoot;
final ViewTreeObserver mViewTreeObserver;
MultiListener(Transition transition, ViewGroup sceneRoot) {
mTransition = transition;
mSceneRoot = sceneRoot;
mViewTreeObserver = mSceneRoot.getViewTreeObserver();
}
private void removeListeners() {
mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
if (mViewTreeObserver.isAlive()) {
mViewTreeObserver.removeOnPreDrawListener(this);
} else {
mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
}
mSceneRoot.removeOnAttachStateChangeListener(this);
}

View File

@@ -0,0 +1,89 @@
/*
* 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 com.android.internal.view;
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.
*/
public 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();
}
}