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