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