diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java new file mode 100644 index 0000000000000..c7da48c112f53 --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java @@ -0,0 +1,238 @@ +/* + * 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[] {1, 6, 10}) { + 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); + } + }); + } + + + 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); + } +}