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.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Activity; 27 import android.content.Context; 28 import android.graphics.Rect; 29 import android.view.ViewTreeObserver.OnDrawListener; 30 import android.widget.FrameLayout; 31 32 import androidx.test.InstrumentationRegistry; 33 import androidx.test.annotation.UiThreadTest; 34 import androidx.test.filters.LargeTest; 35 import androidx.test.rule.ActivityTestRule; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import org.junit.After; 39 import org.junit.Assert; 40 import org.junit.Before; 41 import org.junit.Rule; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.TimeUnit; 47 48 /** 49 * Test of invalidates, drawing, and the flags that support them 50 */ 51 @LargeTest 52 @RunWith(AndroidJUnit4.class) 53 public class ViewInvalidateTest { 54 @Rule 55 public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); 56 57 private static final int INVAL_TEST_FLAG_MASK = View.PFLAG_DIRTY 58 | View.PFLAG_DRAWN 59 | View.PFLAG_DRAWING_CACHE_VALID 60 | View.PFLAG_INVALIDATED 61 | View.PFLAG_DRAW_ANIMATION; 62 63 @Before setup()64 public void setup() throws Throwable { 65 // separate runnable to initialize, so ref is safe to pass to runOnMainAndDrawSync 66 mActivityRule.runOnUiThread(() -> { 67 mParent = new FrameLayout(getContext()); 68 mChild = new View(getContext()); 69 }); 70 71 // attached view is drawn once 72 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 73 mParent.addView(mChild); 74 getActivity().setContentView(mParent); 75 76 // 'invalidated', but not yet drawn 77 validateInvalFlags(mChild, View.PFLAG_INVALIDATED); 78 }); 79 } 80 81 @After teardown()82 public void teardown() { 83 // ensure we don't share views between tests 84 mParent = null; 85 mChild = null; 86 } 87 getContext()88 Context getContext() { 89 return InstrumentationRegistry.getTargetContext(); 90 } 91 getActivity()92 Activity getActivity() { 93 return mActivityRule.getActivity(); 94 } 95 96 private ViewGroup mParent; 97 private View mChild; 98 validateInvalFlags(View view, int... expectedFlagArray)99 private static void validateInvalFlags(View view, int... expectedFlagArray) { 100 int expectedFlags = 0; 101 for (int expectedFlag : expectedFlagArray) { 102 expectedFlags |= expectedFlag; 103 } 104 105 final int observedFlags = view.mPrivateFlags & INVAL_TEST_FLAG_MASK; 106 assertEquals(String.format("expect %x, observed %x", expectedFlags, observedFlags), 107 expectedFlags, observedFlags); 108 } 109 getViewRoot(View view)110 private static ViewRootImpl getViewRoot(View view) { 111 ViewParent parent = view.getParent(); 112 while (parent != null) { 113 if (parent instanceof ViewRootImpl) { 114 return (ViewRootImpl) parent; 115 } 116 parent = parent.getParent(); 117 } 118 return null; 119 } 120 121 @UiThreadTest 122 @Test testInvalidate_behavior()123 public void testInvalidate_behavior() throws Throwable { 124 validateInvalFlags(mChild, 125 View.PFLAG_DRAWING_CACHE_VALID, 126 View.PFLAG_DRAWN); 127 validateInvalFlags(mParent, 128 View.PFLAG_DRAWING_CACHE_VALID, 129 View.PFLAG_DRAWN); 130 assertFalse(getViewRoot(mParent).mTraversalScheduled); 131 132 mChild.invalidate(); 133 134 // no longer drawn, is now invalidated 135 validateInvalFlags(mChild, 136 View.PFLAG_DIRTY, 137 View.PFLAG_INVALIDATED); 138 139 // parent drawing cache no longer valid, marked dirty 140 validateInvalFlags(mParent, 141 View.PFLAG_DRAWN, 142 View.PFLAG_DIRTY); 143 assertTrue(getViewRoot(mParent).mTraversalScheduled); 144 } 145 146 @UiThreadTest 147 @Test testInvalidate_false()148 public void testInvalidate_false() { 149 // Invalidate makes it invalid 150 validateInvalFlags(mChild, 151 View.PFLAG_DRAWING_CACHE_VALID, 152 View.PFLAG_DRAWN); 153 154 mChild.invalidate(/*don't invalidate cache*/ false); 155 156 // drawn is cleared, dirty set, nothing else changed 157 validateInvalFlags(mChild, 158 View.PFLAG_DRAWING_CACHE_VALID, 159 View.PFLAG_DIRTY); 160 } 161 162 @Test testInvalidate_simple()163 public void testInvalidate_simple() throws Throwable { 164 // simple invalidate, which marks the view invalid 165 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 166 validateInvalFlags(mChild, 167 View.PFLAG_DRAWING_CACHE_VALID, 168 View.PFLAG_DRAWN); 169 170 mChild.invalidate(); 171 172 validateInvalFlags(mChild, 173 View.PFLAG_DIRTY, 174 View.PFLAG_INVALIDATED); 175 }); 176 177 // after draw pass, view has drawn, no longer invalid 178 mActivityRule.runOnUiThread(() -> { 179 validateInvalFlags(mChild, 180 View.PFLAG_DRAWING_CACHE_VALID, 181 View.PFLAG_DRAWN); 182 }); 183 } 184 185 @UiThreadTest 186 @Test testInvalidate_manualUpdateDisplayList()187 public void testInvalidate_manualUpdateDisplayList() { 188 // Invalidate makes it invalid 189 validateInvalFlags(mChild, 190 View.PFLAG_DRAWING_CACHE_VALID, 191 View.PFLAG_DRAWN); 192 193 mChild.invalidate(); 194 validateInvalFlags(mChild, 195 View.PFLAG_DIRTY, 196 View.PFLAG_INVALIDATED); 197 198 // updateDisplayListIfDirty makes it valid again, but invalidate still set, 199 // since it's cleared by View#draw(canvas, parent, drawtime) 200 mChild.updateDisplayListIfDirty(); 201 validateInvalFlags(mChild, 202 View.PFLAG_DRAWING_CACHE_VALID, 203 View.PFLAG_DRAWN, 204 View.PFLAG_INVALIDATED); 205 } 206 207 @UiThreadTest 208 @Test testInvalidateChild_simple()209 public void testInvalidateChild_simple() { 210 validateInvalFlags(mParent, 211 View.PFLAG_DRAWING_CACHE_VALID, 212 View.PFLAG_DRAWN); 213 assertFalse(getViewRoot(mParent).mTraversalScheduled); 214 215 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 216 217 validateInvalFlags(mParent, 218 View.PFLAG_DIRTY, 219 View.PFLAG_DRAWN); 220 assertTrue(getViewRoot(mParent).mTraversalScheduled); 221 } 222 223 @Test testInvalidateChild_childHardwareLayer()224 public void testInvalidateChild_childHardwareLayer() throws Throwable { 225 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 226 // do in runnable, so tree won't be dirty 227 mParent.setLayerType(View.LAYER_TYPE_HARDWARE, null); 228 }); 229 230 mActivityRule.runOnUiThread(() -> { 231 validateInvalFlags(mParent, 232 View.PFLAG_DRAWING_CACHE_VALID, 233 View.PFLAG_DRAWN); 234 235 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 236 237 validateInvalFlags(mParent, 238 View.PFLAG_DIRTY, 239 View.PFLAG_DRAWN); // Note: note invalidated, since HW damage handled in native 240 }); 241 } 242 243 @Test testInvalidateChild_childSoftwareLayer()244 public void testInvalidateChild_childSoftwareLayer() throws Throwable { 245 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> { 246 // do in runnable, so tree won't be dirty 247 mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 248 }); 249 250 mActivityRule.runOnUiThread(() -> { 251 validateInvalFlags(mParent, 252 View.PFLAG_DRAWING_CACHE_VALID, 253 View.PFLAG_DRAWN); 254 255 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 256 257 validateInvalFlags(mParent, 258 View.PFLAG_DIRTY, 259 View.PFLAG_DRAWN, 260 View.PFLAG_INVALIDATED); // Note: invalidated, since SW damage handled here 261 }); 262 } 263 264 @UiThreadTest 265 @Test testInvalidateChild_legacyAnimation()266 public void testInvalidateChild_legacyAnimation() throws Throwable { 267 mChild.mPrivateFlags |= View.PFLAG_DRAW_ANIMATION; 268 269 validateInvalFlags(mChild, 270 View.PFLAG_DRAW_ANIMATION, 271 View.PFLAG_DRAWING_CACHE_VALID, 272 View.PFLAG_DRAWN); 273 validateInvalFlags(mParent, 274 View.PFLAG_DRAWING_CACHE_VALID, 275 View.PFLAG_DRAWN); 276 assertFalse(getViewRoot(mParent).mIsAnimating); 277 278 mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1)); 279 280 validateInvalFlags(mChild, 281 View.PFLAG_DRAW_ANIMATION, 282 View.PFLAG_DRAWING_CACHE_VALID, 283 View.PFLAG_DRAWN); 284 validateInvalFlags(mParent, 285 View.PFLAG_DIRTY, 286 View.PFLAG_DRAW_ANIMATION, // carried up to parent 287 View.PFLAG_DRAWN); 288 assertTrue(getViewRoot(mParent).mIsAnimating); 289 } 290 291 /** Copied from cts/common/device-side/util. */ 292 static class WidgetTestUtils { runOnMainAndDrawSync(@onNull final ActivityTestRule activityTestRule, @NonNull final View view, @Nullable final Runnable runner)293 public static void runOnMainAndDrawSync(@NonNull final ActivityTestRule activityTestRule, 294 @NonNull final View view, @Nullable final Runnable runner) throws Throwable { 295 final CountDownLatch latch = new CountDownLatch(1); 296 297 activityTestRule.runOnUiThread(() -> { 298 final OnDrawListener listener = new OnDrawListener() { 299 @Override 300 public void onDraw() { 301 // posting so that the sync happens after the draw that's about to happen 302 view.post(() -> { 303 activityTestRule.getActivity().getWindow().getDecorView() 304 .getViewTreeObserver().removeOnDrawListener(this); 305 latch.countDown(); 306 }); 307 } 308 }; 309 310 activityTestRule.getActivity().getWindow().getDecorView() 311 .getViewTreeObserver().addOnDrawListener(listener); 312 313 if (runner != null) { 314 runner.run(); 315 } 316 view.invalidate(); 317 }); 318 319 try { 320 Assert.assertTrue("Expected draw pass occurred within 5 seconds", 321 latch.await(5, TimeUnit.SECONDS)); 322 } catch (InterruptedException e) { 323 throw new RuntimeException(e); 324 } 325 } 326 327 } 328 } 329