1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import static junit.framework.Assert.assertFalse; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 24 import android.app.Activity; 25 import android.content.Context; 26 import android.graphics.Rect; 27 import android.support.test.InstrumentationRegistry; 28 import android.support.test.annotation.UiThreadTest; 29 import android.support.test.filters.LargeTest; 30 import android.support.test.rule.ActivityTestRule; 31 import android.support.test.runner.AndroidJUnit4; 32 import android.widget.FrameLayout; 33 34 import com.android.compatibility.common.util.WidgetTestUtils; 35 36 import org.junit.After; 37 import org.junit.Before; 38 import org.junit.Rule; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 /** 43 * Test of invalidates, drawing, and the flags that support them 44 */ 45 @LargeTest 46 @RunWith(AndroidJUnit4.class) 47 public class ViewInvalidateTest { 48 @Rule 49 public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); 50 51 private static final int INVAL_TEST_FLAG_MASK = View.PFLAG_DIRTY 52 | View.PFLAG_DIRTY_OPAQUE 53 | View.PFLAG_DRAWN 54 | View.PFLAG_DRAWING_CACHE_VALID 55 | View.PFLAG_INVALIDATED 56 | View.PFLAG_DRAW_ANIMATION; 57 58 @Before 59 public void setup() throws Throwable { 60 // separate runnable to initialize, so ref is safe to pass to runOnMainAndDrawSync 61 mActivityRule.runOnUiThread(() -> { 62 mParent = new FrameLayout(getContext()); 63 mChild = new View(getContext()); 64 }); 65 66 // attached view is drawn once 67 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 68 mParent.addView(mChild); 69 getActivity().setContentView(mParent); 70 71 // 'invalidated', but not yet drawn 72 validateInvalFlags(mChild, View.PFLAG_INVALIDATED); 73 }); 74 } 75 76 @After 77 public void teardown() { 78 // ensure we don't share views between tests 79 mParent = null; 80 mChild = null; 81 } 82 83 Context getContext() { 84 return InstrumentationRegistry.getTargetContext(); 85 } 86 87 Activity getActivity() { 88 return mActivityRule.getActivity(); 89 } 90 91 private ViewGroup mParent; 92 private View mChild; 93 94 private static void validateInvalFlags(View view, int... expectedFlagArray) { 95 int expectedFlags = 0; 96 for (int expectedFlag : expectedFlagArray) { 97 expectedFlags |= expectedFlag; 98 } 99 100 final int observedFlags = view.mPrivateFlags & INVAL_TEST_FLAG_MASK; 101 assertEquals(String.format("expect %x, observed %x", expectedFlags, observedFlags), 102 expectedFlags, observedFlags); 103 } 104 105 private static ViewRootImpl getViewRoot(View view) { 106 ViewParent parent = view.getParent(); 107 while (parent != null) { 108 if (parent instanceof ViewRootImpl) { 109 return (ViewRootImpl) parent; 110 } 111 parent = parent.getParent(); 112 } 113 return null; 114 } 115 116 @UiThreadTest 117 @Test 118 public void testInvalidate_behavior() throws Throwable { 119 validateInvalFlags(mChild, 120 View.PFLAG_DRAWING_CACHE_VALID, 121 View.PFLAG_DRAWN); 122 validateInvalFlags(mParent, 123 View.PFLAG_DRAWING_CACHE_VALID, 124 View.PFLAG_DRAWN); 125 assertFalse(getViewRoot(mParent).mTraversalScheduled); 126 127 mChild.invalidate(); 128 129 // no longer drawn, is now invalidated 130 validateInvalFlags(mChild, 131 View.PFLAG_DIRTY, 132 View.PFLAG_INVALIDATED); 133 134 // parent drawing cache no longer valid, marked dirty 135 validateInvalFlags(mParent, 136 View.PFLAG_DRAWN, 137 View.PFLAG_DIRTY); 138 assertTrue(getViewRoot(mParent).mTraversalScheduled); 139 } 140 141 @UiThreadTest 142 @Test 143 public void testInvalidate_false() { 144 // Invalidate makes it invalid 145 validateInvalFlags(mChild, 146 View.PFLAG_DRAWING_CACHE_VALID, 147 View.PFLAG_DRAWN); 148 149 mChild.invalidate(/*don't invalidate cache*/ false); 150 151 // drawn is cleared, dirty set, nothing else changed 152 validateInvalFlags(mChild, 153 View.PFLAG_DRAWING_CACHE_VALID, 154 View.PFLAG_DIRTY); 155 } 156 157 @Test 158 public void testInvalidate_simple() throws Throwable { 159 // simple invalidate, which marks the view invalid 160 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 161 validateInvalFlags(mChild, 162 View.PFLAG_DRAWING_CACHE_VALID, 163 View.PFLAG_DRAWN); 164 165 mChild.invalidate(); 166 167 validateInvalFlags(mChild, 168 View.PFLAG_DIRTY, 169 View.PFLAG_INVALIDATED); 170 }); 171 172 // after draw pass, view has drawn, no longer invalid 173 mActivityRule.runOnUiThread(() -> { 174 validateInvalFlags(mChild, 175 View.PFLAG_DRAWING_CACHE_VALID, 176 View.PFLAG_DRAWN); 177 }); 178 } 179 180 @UiThreadTest 181 @Test 182 public void testInvalidate_manualUpdateDisplayList() { 183 // Invalidate makes it invalid 184 validateInvalFlags(mChild, 185 View.PFLAG_DRAWING_CACHE_VALID, 186 View.PFLAG_DRAWN); 187 188 mChild.invalidate(); 189 validateInvalFlags(mChild, 190 View.PFLAG_DIRTY, 191 View.PFLAG_INVALIDATED); 192 193 // updateDisplayListIfDirty makes it valid again, but invalidate still set, 194 // since it's cleared by View#draw(canvas, parent, drawtime) 195 mChild.updateDisplayListIfDirty(); 196 validateInvalFlags(mChild, 197 View.PFLAG_DRAWING_CACHE_VALID, 198 View.PFLAG_DRAWN, 199 View.PFLAG_INVALIDATED); 200 } 201 202 @UiThreadTest 203 @Test 204 public void testInvalidateChild_simple() { 205 validateInvalFlags(mParent, 206 View.PFLAG_DRAWING_CACHE_VALID, 207 View.PFLAG_DRAWN); 208 assertFalse(getViewRoot(mParent).mTraversalScheduled); 209 210 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 211 212 validateInvalFlags(mParent, 213 View.PFLAG_DIRTY, 214 View.PFLAG_DRAWN); 215 assertTrue(getViewRoot(mParent).mTraversalScheduled); 216 } 217 218 @Test 219 public void testInvalidateChild_childHardwareLayer() throws Throwable { 220 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 221 // do in runnable, so tree won't be dirty 222 mParent.setLayerType(View.LAYER_TYPE_HARDWARE, null); 223 }); 224 225 mActivityRule.runOnUiThread(() -> { 226 validateInvalFlags(mParent, 227 View.PFLAG_DRAWING_CACHE_VALID, 228 View.PFLAG_DRAWN); 229 230 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 231 232 validateInvalFlags(mParent, 233 View.PFLAG_DIRTY, 234 View.PFLAG_DRAWN); // Note: note invalidated, since HW damage handled in native 235 }); 236 } 237 238 @Test 239 public void testInvalidateChild_childSoftwareLayer() throws Throwable { 240 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 241 // do in runnable, so tree won't be dirty 242 mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 243 }); 244 245 mActivityRule.runOnUiThread(() -> { 246 validateInvalFlags(mParent, 247 View.PFLAG_DRAWING_CACHE_VALID, 248 View.PFLAG_DRAWN); 249 250 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 251 252 validateInvalFlags(mParent, 253 View.PFLAG_DIRTY, 254 View.PFLAG_DRAWN, 255 View.PFLAG_INVALIDATED); // Note: invalidated, since SW damage handled here 256 }); 257 } 258 259 @UiThreadTest 260 @Test 261 public void testInvalidateChild_legacyAnimation() throws Throwable { 262 mChild.mPrivateFlags |= View.PFLAG_DRAW_ANIMATION; 263 264 validateInvalFlags(mChild, 265 View.PFLAG_DRAW_ANIMATION, 266 View.PFLAG_DRAWING_CACHE_VALID, 267 View.PFLAG_DRAWN); 268 validateInvalFlags(mParent, 269 View.PFLAG_DRAWING_CACHE_VALID, 270 View.PFLAG_DRAWN); 271 assertFalse(getViewRoot(mParent).mIsAnimating); 272 273 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 274 275 validateInvalFlags(mChild, 276 View.PFLAG_DRAW_ANIMATION, 277 View.PFLAG_DRAWING_CACHE_VALID, 278 View.PFLAG_DRAWN); 279 validateInvalFlags(mParent, 280 View.PFLAG_DIRTY, 281 View.PFLAG_DRAW_ANIMATION, // carried up to parent 282 View.PFLAG_DRAWN); 283 assertTrue(getViewRoot(mParent).mIsAnimating); 284 } 285 } 286