Home | History | Annotate | Download | only in view
      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