/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static org.junit.Assert.assertTrue; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.StubActivity; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.rule.ActivityTestRule; import android.view.View.MeasureSpec; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.ArrayList; import java.util.List; @RunWith(Parameterized.class) @LargeTest public class ViewShowHidePerfTest { @Rule public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class); @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); public Context getContext() { return InstrumentationRegistry.getInstrumentation().getTargetContext(); } static abstract class SubTreeFactory { String mName; SubTreeFactory(String name) { mName = name; } abstract View create(Context context, int depth); @Override public String toString() { return mName; } } private static SubTreeFactory[] sSubTreeFactories = new SubTreeFactory[] { new SubTreeFactory("NestedLinearLayoutTree") { private int mColorToggle = 0; private void createNestedLinearLayoutTree(Context context, LinearLayout parent, int remainingDepth) { if (remainingDepth <= 0) { mColorToggle = (mColorToggle + 1) % 4; parent.setBackgroundColor((mColorToggle < 2) ? Color.RED : Color.BLUE); return; } boolean vertical = remainingDepth % 2 == 0; parent.setOrientation(vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); for (int i = 0; i < 2; i++) { LinearLayout child = new LinearLayout(context); // vertical: match parent in x axis, horizontal: y axis. parent.addView(child, new LinearLayout.LayoutParams( (vertical ? ViewGroup.LayoutParams.MATCH_PARENT : 0), (vertical ? 0 : ViewGroup.LayoutParams.MATCH_PARENT), 1.0f)); createNestedLinearLayoutTree(context, child, remainingDepth - 1); } } @Override public View create(Context context, int depth) { LinearLayout root = new LinearLayout(context); createNestedLinearLayoutTree(context, root, depth - 1); return root; } }, new SubTreeFactory("ImageViewList") { @Override public View create(Context context, int depth) { LinearLayout root = new LinearLayout(context); root.setOrientation(LinearLayout.HORIZONTAL); int childCount = (int) Math.pow(2, depth); for (int i = 0; i < childCount; i++) { ImageView imageView = new ImageView(context); root.addView(imageView, new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f)); imageView.setImageDrawable(new ColorDrawable(Color.RED)); } return root; } }, }; @Parameterized.Parameters(name = "Factory:{0},depth:{1}") public static Iterable params() { List params = new ArrayList<>(); for (int depth : new int[] { 6 }) { for (SubTreeFactory subTreeFactory : sSubTreeFactories) { params.add(new Object[]{ subTreeFactory, depth }); } } return params; } private final View mChild; public ViewShowHidePerfTest(SubTreeFactory subTreeFactory, int depth) { mChild = subTreeFactory.create(getContext(), depth); } interface TestCallback { void run(BenchmarkState state, int width, int height, ViewGroup parent, View child); } private void testParentWithChild(TestCallback callback) throws Throwable { mActivityRule.runOnUiThread(() -> { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); FrameLayout parent = new FrameLayout(getContext()); mActivityRule.getActivity().setContentView(parent); final int width = 1000; final int height = 1000; layout(width, height, parent); callback.run(state, width, height, parent, mChild); }); } private void updateAndValidateDisplayList(View view) { boolean hasDisplayList = view.updateDisplayListIfDirty().isValid(); assertTrue(hasDisplayList); } private void layout(int width, int height, View view) { view.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); view.layout(0, 0, height, width); } @Test public void testRemove() throws Throwable { testParentWithChild((state, width, height, parent, child) -> { while (state.keepRunning()) { state.pauseTiming(); updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed parent.addView(child); layout(width, height, child); updateAndValidateDisplayList(parent); state.resumeTiming(); parent.removeAllViews(); } }); } @Test public void testAdd() throws Throwable { testParentWithChild((state, width, height, parent, child) -> { while (state.keepRunning()) { state.pauseTiming(); layout(width, height, child); // Note, done to be safe, likely not needed updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed parent.removeAllViews(); updateAndValidateDisplayList(parent); state.resumeTiming(); parent.addView(child); } }); } @Test public void testRecordAfterAdd() throws Throwable { testParentWithChild((state, width, height, parent, child) -> { while (state.keepRunning()) { state.pauseTiming(); parent.removeAllViews(); updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed parent.addView(child); layout(width, height, child); state.resumeTiming(); updateAndValidateDisplayList(parent); } }); } private void testVisibility(int fromVisibility, int toVisibility) throws Throwable { testParentWithChild((state, width, height, parent, child) -> { parent.addView(child); while (state.keepRunning()) { state.pauseTiming(); layout(width, height, parent); updateAndValidateDisplayList(parent); child.setVisibility(fromVisibility); layout(width, height, parent); updateAndValidateDisplayList(parent); state.resumeTiming(); child.setVisibility(toVisibility); } }); } @Test public void testInvisibleToVisible() throws Throwable { testVisibility(View.INVISIBLE, View.VISIBLE); } @Test public void testVisibleToInvisible() throws Throwable { testVisibility(View.VISIBLE, View.INVISIBLE); } @Test public void testGoneToVisible() throws Throwable { testVisibility(View.GONE, View.VISIBLE); } @Test public void testVisibleToGone() throws Throwable { testVisibility(View.VISIBLE, View.GONE); } }