diff options
author | Yury Khmel <khmel@google.com> | 2015-09-02 17:39:14 +0900 |
---|---|---|
committer | Yury Khmel <khmel@google.com> | 2015-09-18 17:04:36 +0900 |
commit | 4f26c041ad0429710d135d8e179eec8f44077ebe (patch) | |
tree | ad3123d6cc0039eb0c8c9b6780f77a36010828bf /tests/WindowAnimationJank/src | |
parent | 686e03454b461484b4b51d0ff10c6361aee8c72b (diff) |
Jank test for full-screen activity orientation change.
Bug: 24142738
Change-Id: Id1a0d9fc78a71812f60d542f2bee91e3ff497ce6
Diffstat (limited to 'tests/WindowAnimationJank/src')
5 files changed, 497 insertions, 0 deletions
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/ElementLayoutActivity.java b/tests/WindowAnimationJank/src/android/windowanimationjank/ElementLayoutActivity.java new file mode 100644 index 000000000000..3b1fabcb59ec --- /dev/null +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/ElementLayoutActivity.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015 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.windowanimationjank; + +import java.util.Random; + +import android.app.Activity; +import android.os.Bundle; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.widget.Chronometer; +import android.widget.RadioButton; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.ToggleButton; + +/* + * Activity with arbitrary number of random UI elements, refresh itself constantly. + */ +public class ElementLayoutActivity extends Activity implements OnPreDrawListener { + public final static String NUM_ELEMENTS_KEY = "num_elements"; + + private final static int DEFAULT_NUM_ELEMENTS = 100; + private final static int BACKGROUND_COLOR = 0xfffff000; + private final static int INDICATOR_COLOR = 0xffff0000; + + private FlowLayout mLayout; + // Use the constant seed in order to get predefined order of elements. + private Random mRandom = new Random(0); + // Blinker indicator for visual feedback that Activity is currently updating. + private TextView mIndicator; + private static float mIndicatorState; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.flowlayout); + + mLayout = (FlowLayout)findViewById(R.id.root_flow_layout); + mLayout.setBackgroundColor(BACKGROUND_COLOR); + + mIndicator = new TextView(this); + mLayout.addView(mIndicator); + mIndicator.setText("***\n***"); + mIndicator.setBackgroundColor(BACKGROUND_COLOR); + mIndicatorState = 0.0f; + + // Need constantly invalidate view in order to get max redraw rate. + mLayout.getViewTreeObserver().addOnPreDrawListener(this); + + // Read requested number of elements in layout. + int numElements = getIntent().getIntExtra(NUM_ELEMENTS_KEY, DEFAULT_NUM_ELEMENTS); + + for (int i = 0; i < numElements; ++i) { + switch (mRandom.nextInt(5)) { + case 0: + createRadioButton(); + break; + case 1: + createToggleButton(); + break; + case 2: + createSwitch(); + break; + case 3: + createTextView(); + break; + case 4: + createChronometer(); + break; + } + } + + setContentView(mLayout); + } + + private void createTextView() { + TextView textView = new TextView(this); + int lineCnt = mRandom.nextInt(4); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < lineCnt; ++i) { + if (i != 0) { + buffer.append("\n"); + } + buffer.append("Line:" + mRandom.nextInt()); + } + textView.setText(buffer); + mLayout.addView(textView); + } + + private void createRadioButton() { + RadioButton button = new RadioButton(this); + button.setText("RadioButton:" + mRandom.nextInt()); + mLayout.addView(button); + } + + private void createToggleButton() { + ToggleButton button = new ToggleButton(this); + button.setChecked(mRandom.nextBoolean()); + mLayout.addView(button); + } + + private void createSwitch() { + Switch button = new Switch(this); + button.setChecked(mRandom.nextBoolean()); + mLayout.addView(button); + } + + private void createChronometer() { + Chronometer chronometer = new Chronometer(this); + chronometer.setBase(mRandom.nextLong()); + mLayout.addView(chronometer); + chronometer.start(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + public boolean onPreDraw() { + // Interpolate indicator color + int background = 0xff000000; + for (int i = 0; i < 3; ++i) { + int shift = 8 * i; + int colorB = (BACKGROUND_COLOR >> shift) & 0xff; + int colorI = (INDICATOR_COLOR >> shift) & 0xff; + int color = (int)((float)colorB * (1.0f - mIndicatorState) + + (float)colorI * mIndicatorState); + if (color > 255) { + color = 255; + } + background |= (color << shift); + } + + mIndicator.setBackgroundColor(background); + mIndicatorState += (3 / 60.0f); // around 3 times per second + mIndicatorState = mIndicatorState - (int)mIndicatorState; + + mLayout.postInvalidate(); + return true; + } +}
\ No newline at end of file diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/FlowLayout.java b/tests/WindowAnimationJank/src/android/windowanimationjank/FlowLayout.java new file mode 100644 index 000000000000..9a2b9ccb4f90 --- /dev/null +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/FlowLayout.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 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.windowanimationjank; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * Custom layout that place all elements in flows with and automatically wraps them. + */ +public class FlowLayout extends ViewGroup { + private int mLineHeight; + + public FlowLayout(Context context) { + super(context); + } + + public FlowLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int width = + MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() -getPaddingRight(); + int height = + MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); + final int count = getChildCount(); + + int x = getPaddingLeft(); + int y = getPaddingTop(); + int lineHeight = 0; + + int childHeightMeasureSpec; + if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); + } else { + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + childHeightMeasureSpec); + final int childWidth = child.getMeasuredWidth(); + lineHeight = Math.max(lineHeight, child.getMeasuredHeight()); + + if (x + childWidth > width) { + x = getPaddingLeft(); + y += lineHeight; + } + + x += childWidth; + } + } + mLineHeight = lineHeight; + + if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { + height = y + lineHeight; + } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { + if (y + lineHeight < height) { + height = y + lineHeight; + } + } + setMeasuredDimension(width, height); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + if (p instanceof LayoutParams) { + return true; + } + return false; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int count = getChildCount(); + final int width = r - l; + int x = getPaddingLeft(); + int y = getPaddingTop(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + if (x + childWidth > width) { + x = getPaddingLeft(); + y += mLineHeight; + } + child.layout(x, y, x + childWidth, y + childHeight); + x += childWidth; + } + } + } +} diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/FullscreenRotationTest.java b/tests/WindowAnimationJank/src/android/windowanimationjank/FullscreenRotationTest.java new file mode 100644 index 000000000000..1fb502a09874 --- /dev/null +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/FullscreenRotationTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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.windowanimationjank; + +import android.os.Bundle; +import android.support.test.jank.JankTest; +import android.support.test.jank.GfxMonitor; + +/** + * Detect janks during screen rotation for full-screen activity. Periodically change + * orientation from left to right and track ElementLayoutActivity rendering performance + * via GfxMonitor. + */ +public class FullscreenRotationTest extends WindowAnimationJankTestBase { + private final static int STEP_CNT = 3; + + @Override + public void beforeTest() throws Exception { + getUiDevice().setOrientationLeft(); + Utils.startElementLayout(getInstrumentation(), 100); + super.beforeTest(); + } + + @Override + public void afterTest(Bundle metrics) { + Utils.rotateDevice(getInstrumentation(), Utils.ROTATION_MODE_NATURAL); + super.afterTest(metrics); + } + + @JankTest(expectedFrames=100, defaultIterationCount=2) + @GfxMonitor(processName=Utils.PACKAGE) + public void testRotation() throws Exception { + for (int i = 0; i < STEP_CNT; ++i) { + Utils.rotateDevice(getInstrumentation(), + Utils.getDeviceRotation(getInstrumentation()) == Utils.ROTATION_MODE_LEFT ? + Utils.ROTATION_MODE_RIGHT : Utils.ROTATION_MODE_LEFT); + } + } +} diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java new file mode 100644 index 000000000000..25314644ca7e --- /dev/null +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 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.windowanimationjank; + +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.content.ComponentName; +import android.content.Intent; +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; + +/** + * Set of helpers to manipulate test activities. + */ +public class Utils { + protected final static String PACKAGE = "android.windowanimationjank"; + protected final static String ELEMENT_LAYOUT_ACTIVITY = "ElementLayoutActivity"; + protected final static String ELEMENT_LAYOUT_CLASS = PACKAGE + "." + ELEMENT_LAYOUT_ACTIVITY; + protected final static long WAIT_FOR_ACTIVITY_TIMEOUT = 10000; + private static final BySelector ROOT_ELEMENT_LAYOUT = By.res(PACKAGE, "root_flow_layout"); + + private final static long ROTATION_ANIMATION_TIME_FULL_SCREEN_MS = 1000; + + protected final static int ROTATION_MODE_NATURAL = 0; + protected final static int ROTATION_MODE_LEFT = 1; + protected final static int ROTATION_MODE_RIGHT = 2; + + private static UiObject2 waitForActivity(Instrumentation instrumentation, BySelector selector) { + UiDevice device = UiDevice.getInstance(instrumentation); + UiObject2 window = device.wait(Until.findObject(selector), WAIT_FOR_ACTIVITY_TIMEOUT); + if (window == null) { + throw new RuntimeException(selector.toString() + " has not been started."); + } + + // Get root object. + while (window.getParent() != null) { + window = window.getParent(); + } + return window; + } + + public static UiObject2 waitForElementLayout(Instrumentation instrumentation) { + return waitForActivity(instrumentation, ROOT_ELEMENT_LAYOUT); + } + + /** + * Start and return activity with requested number of random elements. + */ + public static UiObject2 startElementLayout(Instrumentation instrumentation, int numElements) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName(PACKAGE, ELEMENT_LAYOUT_CLASS)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(ElementLayoutActivity.NUM_ELEMENTS_KEY, numElements); + instrumentation.getTargetContext().startActivity(intent); + return waitForElementLayout(instrumentation); + } + + public static int getDeviceRotation(Instrumentation instrumentation) { + try { + UiDevice device = UiDevice.getInstance(instrumentation); + switch (device.getDisplayRotation()) { + case UiAutomation.ROTATION_FREEZE_90: + return ROTATION_MODE_LEFT; + case UiAutomation.ROTATION_FREEZE_270: + return ROTATION_MODE_RIGHT; + case UiAutomation.ROTATION_FREEZE_0: + case UiAutomation.ROTATION_FREEZE_180: + return ROTATION_MODE_NATURAL; + } + } catch(Exception e) { + throw new RuntimeException(); + } + throw new RuntimeException("Unsupported device rotation."); + } + + public static void rotateDevice(Instrumentation instrumentation, int rotationMode) { + try { + UiDevice device = UiDevice.getInstance(instrumentation); + long startTime = System.currentTimeMillis(); + switch (rotationMode) { + case ROTATION_MODE_NATURAL: + device.setOrientationNatural(); + break; + case ROTATION_MODE_LEFT: + device.setOrientationLeft(); + break; + case ROTATION_MODE_RIGHT: + device.setOrientationRight(); + break; + default: + throw new RuntimeException("Unsupported rotation mode: " + rotationMode); + } + + long toSleep = ROTATION_ANIMATION_TIME_FULL_SCREEN_MS - + (System.currentTimeMillis() - startTime); + if (toSleep > 0) { + SystemClock.sleep(toSleep); + } + } catch(Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java new file mode 100644 index 000000000000..bf739fa8da07 --- /dev/null +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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.windowanimationjank; + +import java.io.IOException; +import java.util.StringTokenizer; + +import android.support.test.jank.JankTestBase; +import android.support.test.uiautomator.UiDevice; + +/** + * This adds additional system level jank monitor and its result is merged with primary monitor + * used in test. + */ +public abstract class WindowAnimationJankTestBase extends JankTestBase { + private static final String TAG = "WindowAnimationJankTestBase"; + + protected WindowAnimationJankTestBase() { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // fix device orientation + getUiDevice().setOrientationNatural(); + + // Start from the home screen + getUiDevice().pressHome(); + getUiDevice().waitForIdle(); + } + + @Override + protected void tearDown() throws Exception { + getUiDevice().unfreezeRotation(); + super.tearDown(); + } + + protected UiDevice getUiDevice() { + return UiDevice.getInstance(getInstrumentation()); + } +} |