Jank test for full-screen activity orientation change.
Bug: 24142738
Change-Id: Id1a0d9fc78a71812f60d542f2bee91e3ff497ce6
diff --git a/tests/WindowAnimationJank/Android.mk b/tests/WindowAnimationJank/Android.mk
new file mode 100644
index 0000000..888ae64
--- /dev/null
+++ b/tests/WindowAnimationJank/Android.mk
@@ -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)
diff --git a/tests/WindowAnimationJank/AndroidManifest.xml b/tests/WindowAnimationJank/AndroidManifest.xml
new file mode 100644
index 0000000..d7aef33
--- /dev/null
+++ b/tests/WindowAnimationJank/AndroidManifest.xml
@@ -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>
diff --git a/tests/WindowAnimationJank/res/layout/flowlayout.xml b/tests/WindowAnimationJank/res/layout/flowlayout.xml
new file mode 100644
index 0000000..f2b559b
--- /dev/null
+++ b/tests/WindowAnimationJank/res/layout/flowlayout.xml
@@ -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" />
\ No newline at end of file
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/ElementLayoutActivity.java b/tests/WindowAnimationJank/src/android/windowanimationjank/ElementLayoutActivity.java
new file mode 100644
index 0000000..3b1fabc
--- /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 0000000..9a2b9cc
--- /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 0000000..1fb502a
--- /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 0000000..2531464
--- /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 0000000..bf739fa
--- /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());
+ }
+}