diff options
author | 2017-01-10 18:15:53 +0000 | |
---|---|---|
committer | 2017-01-10 18:15:57 +0000 | |
commit | 6f225cae06a49bf2c34716b3418d7b64d0feb232 (patch) | |
tree | 887162dcec829ab79b23132bd3fd0f537d5ba359 | |
parent | fb5b1477819f7ae07bd110f8e134e56c6d72d53f (diff) | |
parent | 3f06c6de08b9e0c7ab1fd1774783ca355e994f06 (diff) |
Merge "Revert "Revert "Fast-path for ViewGroup#invalidateChild, invalidate tests"""
-rw-r--r-- | core/java/android/view/View.java | 7 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 125 | ||||
-rw-r--r-- | core/java/android/view/ViewOverlay.java | 3 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 4 | ||||
-rw-r--r-- | core/tests/coretests/Android.mk | 4 | ||||
-rw-r--r-- | core/tests/coretests/src/android/view/ViewInvalidateTest.java | 265 |
6 files changed, 378 insertions, 30 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ecc0f2d8f03c..0b1dfa2a321f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3313,7 +3313,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY_OPAQUE, name = "DIRTY_OPAQUE"), @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY") }, formatToHexString = true) - int mPrivateFlags; + + /* @hide */ + public int mPrivateFlags; int mPrivateFlags2; int mPrivateFlags3; @@ -14110,8 +14112,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * invalidated as well. This is usually true for a full * invalidate, but may be set to false if the View's contents or * dimensions have not changed. + * @hide */ - void invalidate(boolean invalidateCache) { + public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 9ab368d05a57..56501ecd9f4a 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5340,11 +5340,96 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * HW-only, Rect-ignoring invalidation path. + * + * Returns false if this path was unable to complete successfully. This means + * it hit a ViewParent it doesn't recognize and needs to fall back to calculating + * damage area. + * + * Hardware acceleration ignores damage rectangles, since native computes damage for everything + * drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area). + * + * Ignores opaque dirty optimizations, always using the full PFLAG_DIRTY flag. + * + * Ignores FLAG_OPTIMIZE_INVALIDATE, since we're not computing a rect, + * so no point in optimizing that. + * @hide + */ + public boolean tryInvalidateChildHardware(View child) { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo == null || !attachInfo.mHardwareAccelerated) { + return false; + } + + // verify it's ViewGroups up to a ViewRootImpl + ViewRootImpl viewRoot = null; + ViewParent parent = getParent(); + while (parent != null) { + if (parent instanceof ViewGroup) { + parent = parent.getParent(); + } else if (parent instanceof ViewRootImpl) { + viewRoot = (ViewRootImpl) parent; + break; + } else { + // unknown parent type, abort + return false; + } + } + if (viewRoot == null) { + // unable to find ViewRoot + return false; + } + + final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0; + + if (child.mLayerType != LAYER_TYPE_NONE) { + mPrivateFlags |= PFLAG_INVALIDATED; + } + + parent = this; + do { + if (parent != viewRoot) { + // Note: we cast here without checking isinstance, to avoid cost of isinstance again + ViewGroup viewGroup = (ViewGroup) parent; + if (drawAnimation) { + viewGroup.mPrivateFlags |= PFLAG_DRAW_ANIMATION; + } + + // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential + // optimization in provides in a DisplayList world. + viewGroup.mPrivateFlags = + (viewGroup.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY; + + // simplified invalidateChildInParent behavior: clear cache validity to be safe, + // and mark inval if in layer + viewGroup.mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; + if (viewGroup.mLayerType != LAYER_TYPE_NONE) { + viewGroup.mPrivateFlags |= PFLAG_INVALIDATED; + } + } else { + if (drawAnimation) { + viewRoot.mIsAnimating = true; + } + ((ViewRootImpl) parent).invalidate(); + return true; + } + + parent = parent.getParent(); + } while (parent != null); + return true; + } + + + /** * Don't call or override this method. It is used for the implementation of * the view hierarchy. */ @Override public final void invalidateChild(View child, final Rect dirty) { + if (tryInvalidateChildHardware(child)) { + return; + } + ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; @@ -5352,8 +5437,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes // through - final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) - == PFLAG_DRAW_ANIMATION; + final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0; // Check whether the child that requests the invalidate is fully opaque // Views being animated or transformed are not considered opaque because we may @@ -5454,10 +5538,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { - if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN || - (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) { - if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != - FLAG_OPTIMIZE_INVALIDATE) { + if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) { + // either DRAWN, or DRAWING_CACHE_VALID + if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) + != FLAG_OPTIMIZE_INVALIDATE) { dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { @@ -5472,35 +5556,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dirty.setEmpty(); } } - mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; - - if (mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= PFLAG_INVALIDATED; - } - - return mParent; - } else { - mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; - location[CHILD_LEFT_INDEX] = mLeft; - location[CHILD_TOP_INDEX] = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { dirty.set(0, 0, mRight - mLeft, mBottom - mTop); } else { // in case the dirty rect extends outside the bounds of this container dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } + location[CHILD_LEFT_INDEX] = mLeft; + location[CHILD_TOP_INDEX] = mTop; - if (mLayerType != LAYER_TYPE_NONE) { - mPrivateFlags |= PFLAG_INVALIDATED; - } - - return mParent; + mPrivateFlags &= ~PFLAG_DRAWN; + } + mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; + if (mLayerType != LAYER_TYPE_NONE) { + mPrivateFlags |= PFLAG_INVALIDATED; } + + return mParent; } return null; @@ -5513,7 +5590,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * damage area * @hide */ - public boolean damageChildDeferred(View child) { + public boolean damageChildDeferred() { ViewParent parent = getParent(); while (parent != null) { if (parent instanceof ViewGroup) { @@ -5536,7 +5613,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ public void damageChild(View child, final Rect dirty) { - if (damageChildDeferred(child)) { + if (damageChildDeferred()) { return; } diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java index b770bd50c277..61cf0c7555c2 100644 --- a/core/java/android/view/ViewOverlay.java +++ b/core/java/android/view/ViewOverlay.java @@ -283,8 +283,9 @@ public class ViewOverlay { } } + /** @hide */ @Override - void invalidate(boolean invalidateCache) { + public void invalidate(boolean invalidateCache) { super.invalidate(invalidateCache); if (mHostView != null) { mHostView.invalidate(invalidateCache); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 68cd10cf262d..d6db634e6c64 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -240,7 +240,7 @@ public final class ViewRootImpl implements ViewParent, int mWidth; int mHeight; Rect mDirty; - boolean mIsAnimating; + public boolean mIsAnimating; private boolean mDragResizing; private boolean mInvalidateRootRequested; @@ -261,7 +261,7 @@ public final class ViewRootImpl implements ViewParent, final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. - boolean mTraversalScheduled; + public boolean mTraversalScheduled; int mTraversalBarrier; boolean mWillDrawSoon; /** Set to true while in performTraversals for detecting when die(true) is called from internal diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 4699fd5f16e0..a0a9e0129152 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -33,7 +33,9 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target-minus-junit4 \ espresso-core \ ub-uiautomator \ - platform-test-annotations + platform-test-annotations \ + compatibility-device-util + LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common org.apache.http.legacy LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/src/android/view/ViewInvalidateTest.java b/core/tests/coretests/src/android/view/ViewInvalidateTest.java new file mode 100644 index 000000000000..4db70ec4a1c4 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java @@ -0,0 +1,265 @@ +/* + * 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.view; + +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.widget.FrameLayout; + +import com.android.compatibility.common.util.WidgetTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of invalidates, drawing, and the flags that support them + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class ViewInvalidateTest { + @Rule + public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); + + private static final int INVAL_TEST_FLAG_MASK = View.PFLAG_DIRTY + | View.PFLAG_DIRTY_OPAQUE + | View.PFLAG_DRAWN + | View.PFLAG_DRAWING_CACHE_VALID + | View.PFLAG_INVALIDATED + | View.PFLAG_DRAW_ANIMATION; + + @Before + public void setup() throws Throwable { + // separate runnable to initialize, so ref is safe to pass to runOnMainAndDrawSync + mActivityRule.runOnUiThread(() -> { + mParent = new FrameLayout(getContext()); + mChild = new View(getContext()); + }); + + // attached view is drawn once + WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { + mParent.addView(mChild); + getActivity().setContentView(mParent); + + // 'invalidated', but not yet drawn + validateInvalFlags(mChild, View.PFLAG_INVALIDATED); + }); + } + + @After + public void teardown() { + // ensure we don't share views between tests + mParent = null; + mChild = null; + } + + Context getContext() { + return InstrumentationRegistry.getTargetContext(); + } + + Activity getActivity() { + return mActivityRule.getActivity(); + } + + private ViewGroup mParent; + private View mChild; + + private static void validateInvalFlags(View view, int... expectedFlagArray) { + int expectedFlags = 0; + for (int expectedFlag : expectedFlagArray) { + expectedFlags |= expectedFlag; + } + + final int observedFlags = view.mPrivateFlags & INVAL_TEST_FLAG_MASK; + assertEquals(String.format("expect %x, observed %x", expectedFlags, observedFlags), + expectedFlags, observedFlags); + } + + private static ViewRootImpl getViewRoot(View view) { + ViewParent parent = view.getParent(); + while (parent != null) { + if (parent instanceof ViewRootImpl) { + return (ViewRootImpl) parent; + } + parent = parent.getParent(); + } + return null; + } + + @UiThreadTest + @Test + public void testInvalidate_behavior() throws Throwable { + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + validateInvalFlags(mParent, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + assertFalse(getViewRoot(mParent).mTraversalScheduled); + + mChild.invalidate(); + + // no longer drawn, is now invalidated + validateInvalFlags(mChild, + View.PFLAG_DIRTY, + View.PFLAG_INVALIDATED); + + // parent drawing cache no longer valid, marked dirty + validateInvalFlags(mParent, + View.PFLAG_DRAWN, + View.PFLAG_DIRTY); + assertTrue(getViewRoot(mParent).mTraversalScheduled); + } + + @UiThreadTest + @Test + public void testInvalidate_false() { + // Invalidate makes it invalid + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + + mChild.invalidate(/*don't invalidate cache*/ false); + + // drawn is cleared, dirty set, nothing else changed + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DIRTY); + } + + @Test + public void testInvalidate_simple() throws Throwable { + // simple invalidate, which marks the view invalid + WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + + mChild.invalidate(); + + validateInvalFlags(mChild, + View.PFLAG_DIRTY, + View.PFLAG_INVALIDATED); + }); + + // after draw pass, view has drawn, no longer invalid + mActivityRule.runOnUiThread(() -> { + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + }); + } + + @UiThreadTest + @Test + public void testInvalidate_manualUpdateDisplayList() { + // Invalidate makes it invalid + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + + mChild.invalidate(); + validateInvalFlags(mChild, + View.PFLAG_DIRTY, + View.PFLAG_INVALIDATED); + + // updateDisplayListIfDirty makes it valid again, but invalidate still set, + // since it's cleared by View#draw(canvas, parent, drawtime) + mChild.updateDisplayListIfDirty(); + validateInvalFlags(mChild, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN, + View.PFLAG_INVALIDATED); + } + + @UiThreadTest + @Test + public void testInvalidateChild_simple() { + validateInvalFlags(mParent, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + assertFalse(getViewRoot(mParent).mTraversalScheduled); + + mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); + + validateInvalFlags(mParent, + View.PFLAG_DIRTY, + View.PFLAG_DRAWN); + assertTrue(getViewRoot(mParent).mTraversalScheduled); + } + + @Test + public void testInvalidateChild_childHardwareLayer() throws Throwable { + WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { + // do in runnable, so tree won't be dirty + mChild.setLayerType(View.LAYER_TYPE_HARDWARE, null); + }); + + mActivityRule.runOnUiThread(() -> { + validateInvalFlags(mParent, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + + mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); + + validateInvalFlags(mParent, + View.PFLAG_DIRTY, + View.PFLAG_DRAWN, + View.PFLAG_INVALIDATED); + }); + } + + @UiThreadTest + @Test + public void testInvalidateChild_legacyAnimation() throws Throwable { + mChild.mPrivateFlags |= View.PFLAG_DRAW_ANIMATION; + + validateInvalFlags(mChild, + View.PFLAG_DRAW_ANIMATION, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + validateInvalFlags(mParent, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + assertFalse(getViewRoot(mParent).mIsAnimating); + + mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); + + validateInvalFlags(mChild, + View.PFLAG_DRAW_ANIMATION, + View.PFLAG_DRAWING_CACHE_VALID, + View.PFLAG_DRAWN); + validateInvalFlags(mParent, + View.PFLAG_DIRTY, + View.PFLAG_DRAW_ANIMATION, // carried up to parent + View.PFLAG_DRAWN); + assertTrue(getViewRoot(mParent).mIsAnimating); + } +} |