diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp new file mode 100644 index 0000000000000..ff252f717591b --- /dev/null +++ b/tests/ChoreographerTests/Android.bp @@ -0,0 +1,46 @@ +// Copyright 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "ChoreographerTests", + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.ext.junit", + "androidx.test.rules", + "compatibility-device-util-axt", + "com.google.android.material_material", + "truth-prebuilt", + ], + resource_dirs: ["src/main/res"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + optimize: { + enabled: false, + }, +} diff --git a/tests/ChoreographerTests/AndroidManifest.xml b/tests/ChoreographerTests/AndroidManifest.xml new file mode 100644 index 0000000000000..3283c90bd2e55 --- /dev/null +++ b/tests/ChoreographerTests/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/ChoreographerTests/AndroidTest.xml b/tests/ChoreographerTests/AndroidTest.xml new file mode 100644 index 0000000000000..e7176996a49a8 --- /dev/null +++ b/tests/ChoreographerTests/AndroidTest.xml @@ -0,0 +1,30 @@ + + + + \ No newline at end of file diff --git a/tests/ChoreographerTests/OWNERS b/tests/ChoreographerTests/OWNERS new file mode 100644 index 0000000000000..2b7de25555879 --- /dev/null +++ b/tests/ChoreographerTests/OWNERS @@ -0,0 +1,2 @@ +include platform/frameworks/base:/graphics/java/android/graphics/OWNERS +include platform/frameworks/native:/services/surfaceflinger/OWNERS \ No newline at end of file diff --git a/tests/ChoreographerTests/TEST_MAPPING b/tests/ChoreographerTests/TEST_MAPPING new file mode 100644 index 0000000000000..16a48eadc0cd8 --- /dev/null +++ b/tests/ChoreographerTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "ChoreographerTests" + } + ] +} \ No newline at end of file diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java new file mode 100644 index 0000000000000..44112fc3957ce --- /dev/null +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java @@ -0,0 +1,474 @@ +/* + * Copyright 2023 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.choreographertests; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.Manifest; +import android.app.compat.CompatChanges; +import android.hardware.display.DisplayManager; +import android.os.Looper; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import android.view.Choreographer; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class AttachedChoreographerTest { + private static final String TAG = "AttachedChoreographerTest"; + private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758; + private static final int THRESHOLD_MS = 10; + private static final int CALLBACK_TIME_10_FPS = 100; + private static final int CALLBACK_TIME_30_FPS = 33; + private static final int FRAME_ITERATIONS = 21; + private static final int CALLBACK_MISSED_THRESHOLD = 2; + + private final CountDownLatch mTestCompleteSignal = new CountDownLatch(2); + private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1); + private final CountDownLatch mNoCallbackSignal = new CountDownLatch(1); + private final CountDownLatch mFramesSignal = new CountDownLatch(FRAME_ITERATIONS); + + private ActivityScenario mScenario; + private int mInitialMatchContentFrameRate; + private DisplayManager mDisplayManager; + private SurfaceView mSurfaceView; + private SurfaceHolder mSurfaceHolder; + private boolean mIsFirstCallback = true; + private int mCallbackMissedCounter = 0; + + @Before + public void setUp() throws Exception { + mScenario = ActivityScenario.launch(GraphicsActivity.class); + mScenario.moveToState(Lifecycle.State.CREATED); + mCallbackMissedCounter = 0; + mScenario.onActivity(activity -> { + mSurfaceView = activity.findViewById(R.id.surface); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceCreationCountDown.countDown(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + }); + }); + + + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.wakeUp(); + uiDevice.executeShellCommand("wm dismiss-keyguard"); + mScenario.moveToState(Lifecycle.State.RESUMED); + + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG, + Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE, + Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, + Manifest.permission.MANAGE_GAME_MODE); + mScenario.onActivity(activity -> { + mDisplayManager = activity.getSystemService(DisplayManager.class); + mInitialMatchContentFrameRate = toSwitchingType( + mDisplayManager.getMatchContentFrameRateUserPreference()); + mDisplayManager.setRefreshRateSwitchingType( + DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true); + boolean changeIsEnabled = + CompatChanges.isChangeEnabled( + DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID); + Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is " + + (changeIsEnabled ? "enabled" : "disabled")); + }); + } + + @After + public void tearDown() { + mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void test_create_choreographer() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + mTestCompleteSignal.countDown(); + SurfaceControl sc1 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with getChoreographer + sc1.getChoreographer(); + assertTrue(sc1.hasChoreographer()); + assertTrue(sc1.isValid()); + sc1.release(); + + SurfaceControl sc2 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with Looper.myLooper + sc2.getChoreographer(Looper.myLooper()); + assertTrue(sc2.hasChoreographer()); + assertTrue(sc2.isValid()); + sc2.release(); + + SurfaceControl sc3 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with Looper.myLooper + sc3.getChoreographer(Looper.getMainLooper()); + assertTrue(sc3.hasChoreographer()); + assertTrue(sc3.isValid()); + sc3.release(); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_copy_surface_control() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + // Create attached choreographer + sc.getChoreographer(); + assertTrue(sc.hasChoreographer()); + + // Use copy constructor + SurfaceControl copyConstructorSc = new SurfaceControl(sc, "AttachedChoreographerTests"); + //Choreographer isn't copied over. + assertFalse(copyConstructorSc.hasChoreographer()); + copyConstructorSc.getChoreographer(); + assertTrue(copyConstructorSc.hasChoreographer()); + mTestCompleteSignal.countDown(); + + // Use copyFrom + SurfaceControl copyFromSc = new SurfaceControl(); + copyFromSc.copyFrom(sc, "AttachedChoreographerTests"); + //Choreographer isn't copied over. + assertFalse(copyFromSc.hasChoreographer()); + copyFromSc.getChoreographer(); + assertTrue(copyFromSc.hasChoreographer()); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_mirror_surface_control() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + // Create attached choreographer + sc.getChoreographer(); + assertTrue(sc.hasChoreographer()); + mTestCompleteSignal.countDown(); + + // Use mirrorSurface + SurfaceControl mirrorSc = SurfaceControl.mirrorSurface(sc); + //Choreographer isn't copied over. + assertFalse(mirrorSc.hasChoreographer()); + mirrorSc.getChoreographer(); + assertTrue(mirrorSc.hasChoreographer()); + // make SurfaceControl invalid by releasing it. + mirrorSc.release(); + + assertTrue(sc.isValid()); + assertFalse(mirrorSc.isValid()); + assertFalse(mirrorSc.hasChoreographer()); + assertThrows(NullPointerException.class, mirrorSc::getChoreographer); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_postFrameCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postFrameCallback( + frameTimeNanos -> mTestCompleteSignal.countDown()); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postFrameCallback(frameTimeNanos -> mNoCallbackSignal.countDown()); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_postFrameCallbackDelayed() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer(Looper.getMainLooper()).postFrameCallbackDelayed( + callback -> mTestCompleteSignal.countDown(), + /* delayMillis */ 5); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postFrameCallbackDelayed( + frameTimeNanos -> mNoCallbackSignal.countDown(), /* delayMillis */5); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_postCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postCallback(Choreographer.CALLBACK_COMMIT, + mTestCompleteSignal::countDown, /* token */ this); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, + mNoCallbackSignal::countDown, /* token */ this); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_postCallbackDelayed() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postCallbackDelayed(Choreographer.CALLBACK_COMMIT, + mTestCompleteSignal::countDown, /* token */ this, /* delayMillis */ 5); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postCallbackDelayed(Choreographer.CALLBACK_COMMIT, + mNoCallbackSignal::countDown, /* token */ this, /* delayMillis */ 5); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_postVsyncCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeout */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postVsyncCallback(data -> mTestCompleteSignal.countDown()); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postVsyncCallback(data -> mNoCallbackSignal.countDown()); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void test_choreographer_10Hz_refreshRate() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + Choreographer choreographer = sc.getChoreographer(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.setFrameRate(sc, 10.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) + .addTransactionCommittedListener(Runnable::run, + () -> verifyVsyncCallbacks(choreographer, + CALLBACK_TIME_10_FPS)) + .apply(); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + + @Test + public void test_choreographer_30Hz_refreshRate() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + Choreographer choreographer = sc.getChoreographer(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.setFrameRate(sc, 30.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) + .addTransactionCommittedListener(Runnable::run, + () -> verifyVsyncCallbacks(choreographer, + CALLBACK_TIME_30_FPS)) + .apply(); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + + private void verifyVsyncCallbacks(Choreographer choreographer, int callbackDurationMs) { + long callbackRequestedTimeNs = System.nanoTime(); + choreographer.postVsyncCallback(frameData -> { + mFramesSignal.countDown(); + final long frameCount = mFramesSignal.getCount(); + if (frameCount > 0) { + if (!mIsFirstCallback) { + // Skip the first callback as it takes 1 frame + // to reflect the new refresh rate + long callbackDurationDiffMs = getCallbackDurationDiffInMs( + frameData.getFrameTimeNanos(), + callbackRequestedTimeNs, callbackDurationMs); + if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) { + mCallbackMissedCounter++; + Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS) + + " vsync callback failed, expected callback in " + + callbackDurationMs + + " With threshold of " + THRESHOLD_MS + + " but actual duration difference is " + callbackDurationDiffMs); + } + } + mIsFirstCallback = false; + verifyVsyncCallbacks(choreographer, callbackDurationMs); + } else { + assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while " + + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed", + mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD); + mTestCompleteSignal.countDown(); + } + }); + } + + private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs, + int expectedCallbackMs) { + long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs) + - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs); + return Math.abs(expectedCallbackMs - actualTimeMs); + } + + private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) { + try { + return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new AssertionError("Test interrupted", ex); + } + } + + private int toSwitchingType(int matchContentFrameRateUserPreference) { + switch (matchContentFrameRateUserPreference) { + case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER: + return DisplayManager.SWITCHING_TYPE_NONE; + case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY: + return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; + case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS: + return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; + default: + return -1; + } + } + + private void assertDoesReceiveCallback() { + try { + if (mNoCallbackSignal.await(/* timeout */ 50L, TimeUnit.MILLISECONDS)) { + fail("Callback not supposed to be generated"); + } else { + mTestCompleteSignal.countDown(); + } + } catch (InterruptedException e) { + fail("Callback wait is interrupted " + e); + } + } +} diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java new file mode 100644 index 0000000000000..50a68508334e0 --- /dev/null +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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.choreographertests; + +import android.app.Activity; +import android.os.Bundle; + +public class GraphicsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_attached_choreographer); + } +} diff --git a/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml new file mode 100644 index 0000000000000..d6c821228c9a1 --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000..cde69bcccec65 Binary files /dev/null and b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000..c133a0cbd379f Binary files /dev/null and b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000..bfa42f0e7b91d Binary files /dev/null and b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000..324e72cdd7480 Binary files /dev/null and b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/tests/ChoreographerTests/src/main/res/values/strings.xml b/tests/ChoreographerTests/src/main/res/values/strings.xml new file mode 100644 index 0000000000000..e66b001e105ea --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + + ChoreographerTests +