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 org.junit.Assert.assertTrue; 20 21 import android.content.Context; 22 import android.graphics.Color; 23 import android.graphics.drawable.ColorDrawable; 24 import android.perftests.utils.BenchmarkState; 25 import android.perftests.utils.PerfStatusReporter; 26 import android.perftests.utils.StubActivity; 27 import android.view.View.MeasureSpec; 28 import android.widget.FrameLayout; 29 import android.widget.ImageView; 30 import android.widget.LinearLayout; 31 32 import androidx.test.InstrumentationRegistry; 33 import androidx.test.filters.LargeTest; 34 import androidx.test.rule.ActivityTestRule; 35 36 import org.junit.Rule; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.Parameterized; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 @RunWith(Parameterized.class) 45 @LargeTest 46 public class ViewShowHidePerfTest { 47 48 @Rule 49 public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class); 50 51 @Rule 52 public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); 53 54 public Context getContext() { 55 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 56 } 57 58 static abstract class SubTreeFactory { 59 String mName; 60 SubTreeFactory(String name) { mName = name; } 61 62 abstract View create(Context context, int depth); 63 64 @Override 65 public String toString() { 66 return mName; 67 } 68 } 69 70 private static SubTreeFactory[] sSubTreeFactories = new SubTreeFactory[] { 71 new SubTreeFactory("NestedLinearLayoutTree") { 72 private int mColorToggle = 0; 73 74 private void createNestedLinearLayoutTree(Context context, LinearLayout parent, 75 int remainingDepth) { 76 if (remainingDepth <= 0) { 77 mColorToggle = (mColorToggle + 1) % 4; 78 parent.setBackgroundColor((mColorToggle < 2) ? Color.RED : Color.BLUE); 79 return; 80 } 81 82 boolean vertical = remainingDepth % 2 == 0; 83 parent.setOrientation(vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); 84 85 for (int i = 0; i < 2; i++) { 86 LinearLayout child = new LinearLayout(context); 87 // vertical: match parent in x axis, horizontal: y axis. 88 parent.addView(child, new LinearLayout.LayoutParams( 89 (vertical ? ViewGroup.LayoutParams.MATCH_PARENT : 0), 90 (vertical ? 0 : ViewGroup.LayoutParams.MATCH_PARENT), 91 1.0f)); 92 93 createNestedLinearLayoutTree(context, child, remainingDepth - 1); 94 } 95 } 96 97 @Override 98 public View create(Context context, int depth) { 99 LinearLayout root = new LinearLayout(context); 100 createNestedLinearLayoutTree(context, root, depth - 1); 101 return root; 102 } 103 }, 104 new SubTreeFactory("ImageViewList") { 105 @Override 106 public View create(Context context, int depth) { 107 LinearLayout root = new LinearLayout(context); 108 root.setOrientation(LinearLayout.HORIZONTAL); 109 int childCount = (int) Math.pow(2, depth); 110 for (int i = 0; i < childCount; i++) { 111 ImageView imageView = new ImageView(context); 112 root.addView(imageView, new LinearLayout.LayoutParams( 113 0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f)); 114 imageView.setImageDrawable(new ColorDrawable(Color.RED)); 115 } 116 return root; 117 } 118 }, 119 }; 120 121 122 @Parameterized.Parameters(name = "Factory:{0},depth:{1}") 123 public static Iterable<Object[]> params() { 124 List<Object[]> params = new ArrayList<>(); 125 for (int depth : new int[] { 6 }) { 126 for (SubTreeFactory subTreeFactory : sSubTreeFactories) { 127 params.add(new Object[]{ subTreeFactory, depth }); 128 } 129 } 130 return params; 131 } 132 133 private final View mChild; 134 135 public ViewShowHidePerfTest(SubTreeFactory subTreeFactory, int depth) { 136 mChild = subTreeFactory.create(getContext(), depth); 137 } 138 139 interface TestCallback { 140 void run(BenchmarkState state, int width, int height, ViewGroup parent, View child); 141 } 142 143 private void testParentWithChild(TestCallback callback) throws Throwable { 144 mActivityRule.runOnUiThread(() -> { 145 final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 146 147 FrameLayout parent = new FrameLayout(getContext()); 148 mActivityRule.getActivity().setContentView(parent); 149 150 final int width = 1000; 151 final int height = 1000; 152 layout(width, height, parent); 153 154 callback.run(state, width, height, parent, mChild); 155 }); 156 } 157 158 private void updateAndValidateDisplayList(View view) { 159 boolean hasDisplayList = view.updateDisplayListIfDirty().hasDisplayList(); 160 assertTrue(hasDisplayList); 161 } 162 163 private void layout(int width, int height, View view) { 164 view.measure( 165 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 166 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 167 view.layout(0, 0, height, width); 168 } 169 170 @Test 171 public void testRemove() throws Throwable { 172 testParentWithChild((state, width, height, parent, child) -> { 173 while (state.keepRunning()) { 174 state.pauseTiming(); 175 updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed 176 parent.addView(child); 177 layout(width, height, child); 178 updateAndValidateDisplayList(parent); 179 state.resumeTiming(); 180 181 parent.removeAllViews(); 182 } 183 }); 184 } 185 186 @Test 187 public void testAdd() throws Throwable { 188 testParentWithChild((state, width, height, parent, child) -> { 189 while (state.keepRunning()) { 190 state.pauseTiming(); 191 layout(width, height, child); // Note, done to be safe, likely not needed 192 updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed 193 parent.removeAllViews(); 194 updateAndValidateDisplayList(parent); 195 state.resumeTiming(); 196 197 parent.addView(child); 198 } 199 }); 200 } 201 202 @Test 203 public void testRecordAfterAdd() throws Throwable { 204 testParentWithChild((state, width, height, parent, child) -> { 205 while (state.keepRunning()) { 206 state.pauseTiming(); 207 parent.removeAllViews(); 208 updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed 209 parent.addView(child); 210 layout(width, height, child); 211 state.resumeTiming(); 212 213 updateAndValidateDisplayList(parent); 214 } 215 }); 216 } 217 218 private void testVisibility(int fromVisibility, int toVisibility) throws Throwable { 219 testParentWithChild((state, width, height, parent, child) -> { 220 parent.addView(child); 221 222 while (state.keepRunning()) { 223 state.pauseTiming(); 224 layout(width, height, parent); 225 updateAndValidateDisplayList(parent); 226 child.setVisibility(fromVisibility); 227 layout(width, height, parent); 228 updateAndValidateDisplayList(parent); 229 state.resumeTiming(); 230 231 child.setVisibility(toVisibility); 232 } 233 }); 234 } 235 236 @Test 237 public void testInvisibleToVisible() throws Throwable { 238 testVisibility(View.INVISIBLE, View.VISIBLE); 239 } 240 241 @Test 242 public void testVisibleToInvisible() throws Throwable { 243 testVisibility(View.VISIBLE, View.INVISIBLE); 244 } 245 @Test 246 public void testGoneToVisible() throws Throwable { 247 testVisibility(View.GONE, View.VISIBLE); 248 } 249 250 @Test 251 public void testVisibleToGone() throws Throwable { 252 testVisibility(View.VISIBLE, View.GONE); 253 } 254 } 255