Jank test for full-screen activity orientation change.

Bug: 24142738

Change-Id: Id1a0d9fc78a71812f60d542f2bee91e3ff497ce6
This commit is contained in:
Yury Khmel
2015-09-02 17:39:14 +09:00
parent 686e03454b
commit 4f26c041ad
8 changed files with 589 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := WindowAnimationJank
LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.windowanimationjank">
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<application>
<uses-library android:name="android.test.runner"/>
<activity android:name="ElementLayoutActivity"
android:label="ElementLayoutActivity"
android:taskAffinity="android.windowanimationjank.ElementLayoutActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<!-- self-instrumenting test package. -->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="android.windowanimationjank">
</instrumentation>
</manifest>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* 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.
-->
<android.windowanimationjank.FlowLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_flow_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}