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