/* * 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 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 mParent.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); // Note: note invalidated, since HW damage handled in native }); } @Test public void testInvalidateChild_childSoftwareLayer() throws Throwable { WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { // do in runnable, so tree won't be dirty mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, 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); // Note: invalidated, since SW damage handled here }); } @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); } }