diff options
author | 2020-11-11 18:25:48 -0800 | |
---|---|---|
committer | 2020-11-17 11:42:53 -0800 | |
commit | a47f891fdfb5cccc911e8b234f8d9a4424810c3e (patch) | |
tree | 3b5096e280cea67622490cec8b905f616b435f0f | |
parent | f7e5db73c9d884aad142e6822e736f21a005e8ae (diff) |
Add more tests to verify blast buffer queue behaviour
Validate shared buffer mode & auto refresh, max buffer
counts, buffer rejection & geometry.
Test: atest SurfaceViewBufferTests
Bug: 169849887
Change-Id: I322b62f1e0a8f13f68f4e70c8ef1e33a8d6217f5
16 files changed, 1104 insertions, 203 deletions
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp index 647da2abd213..48031de15f54 100644 --- a/tests/SurfaceViewBufferTests/Android.bp +++ b/tests/SurfaceViewBufferTests/Android.bp @@ -33,6 +33,8 @@ android_test { "kotlinx-coroutines-android", "flickerlib", "truth-prebuilt", + "cts-wm-util", + "CtsSurfaceValidatorLib", ], } @@ -43,6 +45,7 @@ cc_library_shared { ], shared_libs: [ "libutils", + "libui", "libgui", "liblog", "libandroid", diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml index 95885c1ca635..c910ecdac1b3 100644 --- a/tests/SurfaceViewBufferTests/AndroidManifest.xml +++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml @@ -23,12 +23,19 @@ <uses-permission android:name="android.permission.DUMP" /> <!-- Enable / Disable sv blast adapter !--> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <!-- Readback virtual display output !--> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + <!-- Save failed test bitmap images !--> + <uses-permission android:name="android.Manifest.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="false" android:supportsRtl="true"> <activity android:name=".MainActivity" android:taskAffinity="com.android.test.MainActivity" android:theme="@style/AppTheme" + android:configChanges="orientation|screenSize" android:label="SurfaceViewBufferTestApp" android:exported="true"> <intent-filter> @@ -36,6 +43,10 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" + android:foregroundServiceType="mediaProjection" + android:enabled="true"> + </service> <uses-library android:name="android.test.runner"/> </application> diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp index 0c86524293e7..ce226fdce320 100644 --- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp +++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp @@ -32,6 +32,7 @@ extern "C" { int i = 0; static ANativeWindow* sAnw; +static std::map<uint32_t /* slot */, ANativeWindowBuffer*> sBuffers; JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass, jobject surfaceObject) { @@ -39,11 +40,14 @@ JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env assert(sAnw); android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); surface->enableFrameTimestamps(true); + surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false); + native_window_set_usage(sAnw, GRALLOC_USAGE_SW_WRITE_OFTEN); + native_window_set_buffers_format(sAnw, HAL_PIXEL_FORMAT_RGBA_8888); return 0; } JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed( - JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) { + JNIEnv*, jclass, jlong jFrameNumber, jint timeoutMs) { using namespace std::chrono_literals; assert(sAnw); android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); @@ -63,8 +67,8 @@ JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplay &outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime); if (outDisplayPresentTime < 0) { auto end = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() > - timeoutSec) { + if (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() > + timeoutMs) { return -1; } } @@ -99,4 +103,121 @@ JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffer assert(sAnw); return ANativeWindow_setBuffersGeometry(sAnw, w, h, format); } + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersTransform( + JNIEnv* /* env */, jclass /* clazz */, jint transform) { + assert(sAnw); + return native_window_set_buffers_transform(sAnw, transform); +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetScalingMode(JNIEnv* /* env */, + jclass /* clazz */, + jint scalingMode) { + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + return surface->setScalingMode(scalingMode); +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceDequeueBuffer(JNIEnv* /* env */, + jclass /* clazz */, + jint slot, + jint timeoutMs) { + assert(sAnw); + ANativeWindowBuffer* anb; + int fenceFd; + int result = sAnw->dequeueBuffer(sAnw, &anb, &fenceFd); + if (result != android::OK) { + return result; + } + sBuffers[slot] = anb; + android::sp<android::Fence> fence(new android::Fence(fenceFd)); + int waitResult = fence->wait(timeoutMs); + if (waitResult != android::OK) { + sAnw->cancelBuffer(sAnw, sBuffers[slot], -1); + sBuffers[slot] = nullptr; + return waitResult; + } + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceCancelBuffer(JNIEnv* /* env */, + jclass /* clazz */, + jint slot) { + assert(sAnw); + assert(sBuffers[slot]); + int result = sAnw->cancelBuffer(sAnw, sBuffers[slot], -1); + sBuffers[slot] = nullptr; + return result; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_drawBuffer(JNIEnv* env, + jclass /* clazz */, jint slot, + jintArray jintArrayColor) { + assert(sAnw); + assert(sBuffers[slot]); + + int* color = env->GetIntArrayElements(jintArrayColor, nullptr); + + ANativeWindowBuffer* buffer = sBuffers[slot]; + android::sp<android::GraphicBuffer> graphicBuffer(static_cast<android::GraphicBuffer*>(buffer)); + const android::Rect bounds(buffer->width, buffer->height); + android::Region newDirtyRegion; + newDirtyRegion.set(bounds); + + void* vaddr; + int fenceFd = -1; + graphicBuffer->lockAsync(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, + newDirtyRegion.bounds(), &vaddr, fenceFd); + + for (int32_t row = 0; row < buffer->height; row++) { + uint8_t* dst = static_cast<uint8_t*>(vaddr) + (buffer->stride * row) * 4; + for (int32_t column = 0; column < buffer->width; column++) { + dst[0] = color[0]; + dst[1] = color[1]; + dst[2] = color[2]; + dst[3] = color[3]; + dst += 4; + } + } + graphicBuffer->unlockAsync(&fenceFd); + env->ReleaseIntArrayElements(jintArrayColor, color, JNI_ABORT); + return 0; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceQueueBuffer(JNIEnv* /* env */, + jclass /* clazz */, + jint slot, + jboolean freeSlot) { + assert(sAnw); + assert(sBuffers[slot]); + int result = sAnw->queueBuffer(sAnw, sBuffers[slot], -1); + if (freeSlot) { + sBuffers[slot] = nullptr; + } + return result; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount( + JNIEnv* /* env */, jclass /* clazz */, jint count) { + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + int result = native_window_set_buffer_count(sAnw, count); + return result; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetSharedBufferMode( + JNIEnv* /* env */, jclass /* clazz */, jboolean shared) { + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + int result = native_window_set_shared_buffer_mode(sAnw, shared); + return result; +} + +JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetAutoRefresh( + JNIEnv* /* env */, jclass /* clazz */, jboolean autoRefresh) { + assert(sAnw); + android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw); + int result = native_window_set_auto_refresh(sAnw, autoRefresh); + return result; +} }
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt new file mode 100644 index 000000000000..eb16bad81d19 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) { + /** Submit buffers as fast as possible and make sure they are presented on display */ + @Test + fun testQueueBuffers() { + val numFrames = 100L + val trace = withTrace { + for (i in 1..numFrames) { + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + } + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 1000 /* ms */)) + } + + assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + } + + @Test + fun testSetBufferScalingMode_outOfOrderQueueBuffer() { + val trace = withTrace { + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + + it.mSurfaceProxy.SurfaceQueueBuffer(1) + it.mSurfaceProxy.SurfaceQueueBuffer(0) + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(2, 5000 /* ms */)) + } + + assertThat(trace).hasFrameSequence("SurfaceView", 1..2L) + } + + @Test + fun testSetBufferScalingMode_multipleDequeueBuffer() { + val numFrames = 20L + val trace = withTrace { + for (count in 1..(numFrames / 2)) { + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + + it.mSurfaceProxy.SurfaceQueueBuffer(0) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + } + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */)) + } + + assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + } + + @Test + fun testSetBufferCount_queueMaxBufferCountMinusOne() { + val numBufferCount = 8 + val numFrames = numBufferCount * 5L + val trace = withTrace { + assertEquals(0, it.mSurfaceProxy.NativeWindowSetBufferCount(numBufferCount + 1)) + for (i in 1..numFrames / numBufferCount) { + for (bufferSlot in 0..numBufferCount - 1) { + assertEquals(0, + it.mSurfaceProxy.SurfaceDequeueBuffer(bufferSlot, 1000 /* ms */)) + } + + for (bufferSlot in 0..numBufferCount - 1) { + it.mSurfaceProxy.SurfaceQueueBuffer(bufferSlot) + } + } + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */)) + } + + assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt new file mode 100644 index 000000000000..95a7fd5b7a39 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.graphics.Point +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode +import com.android.test.SurfaceViewBufferTestBase.Companion.Transform +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) { + @Test + fun testSetBuffersGeometry_0x0_rejectsBuffer() { + val trace = withTrace { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100, + R8G8B8A8_UNORM) + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM) + // Submit buffer one with a different size which should be rejected + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + + // submit a buffer with the default buffer size + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */) + } + // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + + // Verify the next buffer is submitted with the correct size + assertThat(trace).layer("SurfaceView", 3).also { + it.hasBufferSize(defaultBufferSize) + // scaling mode is not passed down to the layer for blast + if (useBlastAdapter) { + it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal) + } else { + it.hasScalingMode(ScalingMode.FREEZE.ordinal) + } + } + } + + @Test + fun testSetBufferScalingMode_freeze() { + val bufferSize = Point(300, 200) + val trace = withTrace { + it.drawFrame() + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0) + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize, + R8G8B8A8_UNORM) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + // Change buffer size and set scaling mode to freeze + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0), + R8G8B8A8_UNORM) + + // first dequeued buffer does not have the new size so it should be rejected. + it.mSurfaceProxy.SurfaceQueueBuffer(0) + it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0) + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) + } + + @Test + fun testSetBufferScalingMode_freeze_withBufferRotation() { + val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x) + val trace = withTrace { + it.drawFrame() + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0) + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize, + R8G8B8A8_UNORM) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + // Change buffer size and set scaling mode to freeze + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0), + R8G8B8A8_UNORM) + + // first dequeued buffer does not have the new size so it should be rejected. + it.mSurfaceProxy.SurfaceQueueBuffer(0) + // add a buffer transform so the buffer size is correct. + it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0) + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) + assertThat(trace).layer("SurfaceView", 3).hasBufferOrientation(Transform.ROT_90.value) + } + + @Test + fun testRejectedBuffersAreReleased() { + val bufferSize = Point(300, 200) + val trace = withTrace { + for (count in 0 until 5) { + it.drawFrame() + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 1L, + 500 /* ms */), 0) + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize, + R8G8B8A8_UNORM) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + // Change buffer size and set scaling mode to freeze + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0), + R8G8B8A8_UNORM) + + // first dequeued buffer does not have the new size so it should be rejected. + it.mSurfaceProxy.SurfaceQueueBuffer(0) + it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed((count * 3) + 3L, + 500 /* ms */), 0) + } + } + + for (count in 0 until 5) { + assertThat(trace).layer("SurfaceView", (count * 3) + 1L) + .hasBufferSize(defaultBufferSize) + assertThat(trace).layer("SurfaceView", (count * 3) + 2L) + .doesNotExist() + assertThat(trace).layer("SurfaceView", (count * 3) + 3L) + .hasBufferSize(bufferSize) + } + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt new file mode 100644 index 000000000000..03f8c05346b7 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.graphics.Point +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode +import com.android.test.SurfaceViewBufferTestBase.Companion.Transform +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) { + @Test + fun testSetBuffersGeometry_0x0_resetsBufferSize() { + val trace = withTrace { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, + R8G8B8A8_UNORM) + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */) + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + } + + @Test + fun testSetBuffersGeometry_smallerThanBuffer() { + val bufferSize = Point(300, 200) + val trace = withTrace { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize, + R8G8B8A8_UNORM) + it.drawFrame() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */) + } + + assertThat(trace).layer("SurfaceView", 1).also { + it.hasBufferSize(bufferSize) + it.hasLayerSize(defaultBufferSize) + it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal) + } + } + + @Test + fun testSetBuffersGeometry_largerThanBuffer() { + val bufferSize = Point(3000, 2000) + val trace = withTrace { + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize, + R8G8B8A8_UNORM) + it.drawFrame() + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */) + } + + assertThat(trace).layer("SurfaceView", 1).also { + it.hasBufferSize(bufferSize) + it.hasLayerSize(defaultBufferSize) + it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal) + } + } + + @Test + fun testSetBufferScalingMode_freeze() { + val bufferSize = Point(300, 200) + val trace = withTrace { + it.drawFrame() + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0) + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize, + R8G8B8A8_UNORM) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + // Change buffer size and set scaling mode to freeze + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0), + R8G8B8A8_UNORM) + + // first dequeued buffer does not have the new size so it should be rejected. + it.mSurfaceProxy.SurfaceQueueBuffer(0) + it.mSurfaceProxy.SurfaceSetScalingMode(ScalingMode.SCALE_TO_WINDOW) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0) + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) + } + + @Test + fun testSetBuffersTransform_FLIP() { + val transforms = arrayOf(Transform.FLIP_H, Transform.FLIP_V, Transform.ROT_180).withIndex() + for ((index, transform) in transforms) { + val trace = withTrace { + it.mSurfaceProxy.ANativeWindowSetBuffersTransform(transform) + it.mSurfaceProxy.ANativeWindowLock() + it.mSurfaceProxy.ANativeWindowUnlockAndPost() + it.mSurfaceProxy.waitUntilBufferDisplayed(index + 1L, 500 /* ms */) + } + + assertThat(trace).layer("SurfaceView", index + 1L).also { + it.hasBufferSize(defaultBufferSize) + it.hasLayerSize(defaultBufferSize) + it.hasBufferOrientation(transform.value) + } + } + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt new file mode 100644 index 000000000000..eac30417dfae --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.graphics.Point +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import com.android.test.SurfaceViewBufferTestBase.Companion.Transform +import junit.framework.Assert.assertEquals +import org.junit.Assume.assumeFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class InverseDisplayTransformTests(useBlastAdapter: Boolean) : + SurfaceTracingTestBase(useBlastAdapter) { + @Before + override fun setup() { + scenarioRule.getScenario().onActivity { + it.rotate90() + } + instrumentation.waitForIdleSync() + super.setup() + } + + @Test + fun testSetBufferScalingMode_freeze_withInvDisplayTransform() { + assumeFalse("Blast does not support buffer rejection with Inv display " + + "transform since the only user for this hidden api is camera which does not use" + + "fixed scaling mode.", useBlastAdapter) + + val rotatedBufferSize = Point(defaultBufferSize.y, defaultBufferSize.x) + val trace = withTrace { + // Inverse display transforms are sticky AND they are only consumed by the sf after + // a valid buffer has been acquired. + it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.INVERSE_DISPLAY.value) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + it.mSurfaceProxy.SurfaceQueueBuffer(0) + + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */), 0) + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, rotatedBufferSize, + R8G8B8A8_UNORM) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1000 /* ms */)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(1, 1000 /* ms */)) + // Change buffer size and set scaling mode to freeze + it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, Point(0, 0), + R8G8B8A8_UNORM) + + // first dequeued buffer does not have the new size so it should be rejected. + it.mSurfaceProxy.ANativeWindowSetBuffersTransform(Transform.ROT_90.value) + it.mSurfaceProxy.SurfaceQueueBuffer(0) + it.mSurfaceProxy.ANativeWindowSetBuffersTransform(0) + it.mSurfaceProxy.SurfaceQueueBuffer(1) + assertEquals(it.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */), 0) + } + + // verify buffer size is reset to default buffer size + assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + assertThat(trace).layer("SurfaceView", 2).doesNotExist() + assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt index b1e1336c4f6d..ed79054409ea 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt @@ -15,51 +15,80 @@ */ package com.android.test -import android.app.Activity import android.content.Context +import android.content.pm.ActivityInfo +import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.graphics.Color import android.graphics.Paint +import android.graphics.Point import android.graphics.Rect import android.os.Bundle import android.view.Gravity import android.view.Surface import android.view.SurfaceHolder import android.view.SurfaceView +import android.view.View +import android.view.WindowManager +import android.view.cts.surfacevalidator.CapturedActivity import android.widget.FrameLayout import java.util.concurrent.CountDownLatch import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -class MainActivity : Activity() { +class MainActivity : CapturedActivity() { val mSurfaceProxy = SurfaceProxy() private var mSurfaceHolder: SurfaceHolder? = null private val mDrawLock = ReentrantLock() + var mSurfaceView: SurfaceView? = null val surface: Surface? get() = mSurfaceHolder?.surface - public override fun onCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - addSurfaceView(Rect(0, 0, 500, 200)) + addSurfaceView(Point(500, 200)) + window.decorView.apply { + systemUiVisibility = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN + } + } + + override fun getCaptureDurationMs(): Long { + return 30000 } - fun addSurfaceView(size: Rect): CountDownLatch { + fun addSurfaceView(size: Point): CountDownLatch { val layout = findViewById<FrameLayout>(android.R.id.content) val surfaceReadyLatch = CountDownLatch(1) - val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch) - layout.addView(surfaceView, - FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT) + mSurfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch) + layout.addView(mSurfaceView!!, + FrameLayout.LayoutParams(size.x, size.y, Gravity.TOP or Gravity.LEFT) .also { it.setMargins(100, 100, 0, 0) }) + return surfaceReadyLatch } + fun enableSeamlessRotation() { + val p: WindowManager.LayoutParams = window.attributes + p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS + window.attributes = p + } + + fun rotate90() { + if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + } + } + private fun createSurfaceView( context: Context, - size: Rect, + size: Point, surfaceReadyLatch: CountDownLatch ): SurfaceView { val surfaceView = SurfaceView(context) surfaceView.setWillNotDraw(false) - surfaceView.holder.setFixedSize(size.width(), size.height()) + surfaceView.holder.setFixedSize(size.x, size.y) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { mDrawLock.withLock { diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt new file mode 100644 index 000000000000..df3d30e13908 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.annotation.ColorInt +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.server.wm.WindowManagerState.getLogicalDisplaySize +import android.view.cts.surfacevalidator.CapturedActivity +import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase +import android.view.cts.surfacevalidator.PixelChecker +import android.view.cts.surfacevalidator.RectChecker +import android.widget.FrameLayout +import androidx.test.rule.ActivityTestRule +import org.junit.After +import org.junit.Before +import org.junit.Rule +import java.util.concurrent.CountDownLatch + +open class ScreenRecordTestBase(useBlastAdapter: Boolean) : + SurfaceViewBufferTestBase(useBlastAdapter) { + @get:Rule + var mActivityRule = ActivityTestRule(MainActivity::class.java) + + private lateinit var mActivity: MainActivity + + @Before + override fun setup() { + super.setup() + mActivity = mActivityRule.launchActivity(Intent()) + lateinit var surfaceReadyLatch: CountDownLatch + runOnUiThread { + it.dismissPermissionDialog() + it.setLogicalDisplaySize(getLogicalDisplaySize()) + surfaceReadyLatch = it.addSurfaceView(defaultBufferSize) + } + surfaceReadyLatch.await() + // sleep to finish animations + instrumentation.waitForIdleSync() + } + + @After + override fun teardown() { + super.teardown() + mActivityRule.finishActivity() + } + + fun runOnUiThread(predicate: (it: MainActivity) -> Unit) { + mActivityRule.runOnUiThread { + predicate(mActivity) + } + } + + fun withScreenRecording( + boundsToCheck: Rect, + @ColorInt color: Int, + predicate: (it: MainActivity) -> Unit + ): CapturedActivity.TestResult { + val testCase = object : ISurfaceValidatorTestCase { + override fun getChecker(): PixelChecker = RectChecker(boundsToCheck, color) + override fun start(context: Context, parent: FrameLayout) { + predicate(mActivity) + } + override fun end() { /* do nothing */ } + } + + return mActivity.runTest(testCase) + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt new file mode 100644 index 000000000000..996a1d3d79da --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.graphics.Color +import android.graphics.Rect +import android.os.SystemClock +import android.view.cts.surfacevalidator.PixelColor +import junit.framework.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class SharedBufferModeScreenRecordTests(useBlastAdapter: Boolean) : + ScreenRecordTestBase(useBlastAdapter) { + + /** When auto refresh is set, surface flinger will wake up and refresh the display presenting + * the latest content in the buffer. + */ + @Test + fun testAutoRefresh() { + var svBounds = Rect() + runOnUiThread { + assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true)) + assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */)) + it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */) + assertEquals(0, + it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */)) + + svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height) + val position = Rect() + it.mSurfaceView!!.getBoundsOnScreen(position) + svBounds.offsetTo(position.left, position.top) + + // wait for buffers from other layers to be latched and transactions to be processed before + // updating the buffer + SystemClock.sleep(4000) + } + + val result = withScreenRecording(svBounds, PixelColor.RED) { + it.mSurfaceProxy.drawBuffer(0, Color.RED) + } + val failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames) + + assertTrue("Error: " + result.failFrames + + " incorrect frames observed (out of " + (result.failFrames + result.passFrames) + + " frames)", failRatio < 0.05) + assertTrue("Error: Did not receive sufficient frame updates expected: >1000 actual:" + + result.passFrames, result.passFrames > 1000) + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt new file mode 100644 index 000000000000..ae662506bc77 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.graphics.Color +import android.graphics.Rect +import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastAdapter) { + /** Sanity test to check each buffer is presented if its submitted with enough delay + * for SF to present the buffers. */ + @Test + fun testCanPresentBuffers() { + val numFrames = 15L + val trace = withTrace { + assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true)) + for (i in 1..numFrames) { + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */)) + it.mSurfaceProxy.SurfaceQueueBuffer(0) + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(i, 5000 /* ms */)) + } + } + + assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + } + + /** Submit buffers as fast as possible testing that we are not blocked when dequeuing the buffer + * by setting the dequeue timeout to 1ms and checking that we present the newest buffer. */ + @Test + fun testFastQueueBuffers() { + val numFrames = 15L + val trace = withTrace { + assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true)) + for (i in 1..numFrames) { + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */)) + it.mSurfaceProxy.SurfaceQueueBuffer(0) + } + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(numFrames, 5000 /* ms */)) + } + + assertThat(trace).hasFrameSequence("SurfaceView", numFrames..numFrames) + } + + /** Keep overwriting the buffer without queuing buffers and check that we present the latest + * buffer content. */ + @Test + fun testAutoRefresh() { + var svBounds = Rect() + runOnUiThread { + assertEquals(0, it.mSurfaceProxy.NativeWindowSetSharedBufferMode(true)) + assertEquals(0, it.mSurfaceProxy.NativeWindowSetAutoRefresh(true)) + assertEquals(0, it.mSurfaceProxy.SurfaceDequeueBuffer(0, 1 /* ms */)) + it.mSurfaceProxy.SurfaceQueueBuffer(0, false /* freeSlot */) + assertEquals(0, it.mSurfaceProxy.waitUntilBufferDisplayed(1, 5000 /* ms */)) + + svBounds = Rect(0, 0, it.mSurfaceView!!.width, it.mSurfaceView!!.height) + val position = Rect() + it.mSurfaceView!!.getBoundsOnScreen(position) + svBounds.offsetTo(position.left, position.top) + } + + runOnUiThread { + it.mSurfaceProxy.drawBuffer(0, Color.RED) + checkPixels(svBounds, Color.RED) + it.mSurfaceProxy.drawBuffer(0, Color.GREEN) + checkPixels(svBounds, Color.GREEN) + it.mSurfaceProxy.drawBuffer(0, Color.BLUE) + checkPixels(svBounds, Color.BLUE) + } + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt index 884aae41446c..cfbd3ac11acb 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt @@ -16,17 +16,46 @@ package com.android.test +import android.annotation.ColorInt +import android.graphics.Color +import android.graphics.Point +import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode +import com.android.test.SurfaceViewBufferTestBase.Companion.Transform + class SurfaceProxy { init { System.loadLibrary("surface_jni") } external fun setSurface(surface: Any) - external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int) + external fun waitUntilBufferDisplayed(frameNumber: Long, timeoutMs: Int): Int external fun draw() + fun drawBuffer(slot: Int, @ColorInt c: Int) { + drawBuffer(slot, intArrayOf(Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))) + } + external fun drawBuffer(slot: Int, color: IntArray) // android/native_window.h functions external fun ANativeWindowLock() external fun ANativeWindowUnlockAndPost() + fun ANativeWindowSetBuffersGeometry(surface: Any, size: Point, format: Int) { + ANativeWindowSetBuffersGeometry(surface, size.x, size.y, format) + } external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int) + fun ANativeWindowSetBuffersTransform(transform: Transform) { + ANativeWindowSetBuffersTransform(transform.value) + } + external fun ANativeWindowSetBuffersTransform(transform: Int) + + // gui/Surface.h functions + fun SurfaceSetScalingMode(scalingMode: ScalingMode) { + SurfaceSetScalingMode(scalingMode.ordinal) + } + external fun SurfaceSetScalingMode(scalingMode: Int) + external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int + external fun SurfaceCancelBuffer(slot: Int) + external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true) + external fun NativeWindowSetBufferCount(count: Int): Int + external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int + external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int } diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt new file mode 100644 index 000000000000..cd4b38516bc3 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.annotation.ColorInt +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Rect +import android.util.Log +import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.android.server.wm.flicker.monitor.LayersTraceMonitor +import com.android.server.wm.flicker.monitor.withSFTracing +import com.android.server.wm.flicker.traces.layers.LayersTrace +import junit.framework.Assert +import org.junit.After +import org.junit.Before +import org.junit.Rule +import java.io.FileOutputStream +import java.io.IOException +import java.util.concurrent.CountDownLatch + +open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : + SurfaceViewBufferTestBase(useBlastAdapter) { + @get:Rule + var scenarioRule: ActivityScenarioRule<MainActivity> = + ActivityScenarioRule<MainActivity>(MainActivity::class.java) + + @Before + override fun setup() { + super.setup() + stopLayerTrace() + addSurfaceView() + } + + @After + override fun teardown() { + super.teardown() + scenarioRule.getScenario().close() + } + + fun withTrace(predicate: (it: MainActivity) -> Unit): LayersTrace { + return withSFTracing(instrumentation, TRACE_FLAGS) { + scenarioRule.getScenario().onActivity { + predicate(it) + } + } + } + + fun runOnUiThread(predicate: (it: MainActivity) -> Unit) { + scenarioRule.getScenario().onActivity { + predicate(it) + } + } + + private fun addSurfaceView() { + lateinit var surfaceReadyLatch: CountDownLatch + scenarioRule.getScenario().onActivity { + surfaceReadyLatch = it.addSurfaceView(defaultBufferSize) + } + surfaceReadyLatch.await() + // sleep to finish animations + instrumentation.waitForIdleSync() + } + + private fun stopLayerTrace() { + val tmpDir = instrumentation.targetContext.dataDir.toPath() + LayersTraceMonitor(tmpDir).stop() + } + + fun checkPixels(bounds: Rect, @ColorInt color: Int) { + val screenshot = instrumentation.getUiAutomation().takeScreenshot() + val pixels = IntArray(screenshot.width * screenshot.height) + screenshot.getPixels(pixels, 0, screenshot.width, 0, 0, screenshot.width, screenshot.height) + for (i in bounds.left + 10..bounds.right - 10) { + for (j in bounds.top + 10..bounds.bottom - 10) { + val actualColor = pixels[j * screenshot.width + i] + if (actualColor != color) { + val screenshotPath = instrumentation.targetContext + .getExternalFilesDir(null)?.resolve("screenshot.png") + try { + FileOutputStream(screenshotPath).use { out -> + screenshot.compress(Bitmap.CompressFormat.PNG, 100, out) + } + Log.e("SurfaceViewBufferTests", "Bitmap written to $screenshotPath") + } catch (e: IOException) { + Log.e("SurfaceViewBufferTests", "Error writing bitmap to file", e) + } + } + Assert.assertEquals("Checking $bounds found mismatch $i,$j", + Color.valueOf(color), Color.valueOf(actualColor)) + } + } + } + + private companion object { + private const val TRACE_FLAGS = + (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC + } +}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt deleted file mode 100644 index b48a91d49b91..000000000000 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2020 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 com.android.test - -import android.app.Instrumentation -import android.graphics.Rect -import android.provider.Settings -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.monitor.LayersTraceMonitor -import com.android.server.wm.flicker.monitor.withSFTracing -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.concurrent.CountDownLatch -import kotlin.properties.Delegates - -@RunWith(Parameterized::class) -class SurfaceViewBufferTest(val useBlastAdapter: Boolean) { - private var mInitialUseBlastConfig by Delegates.notNull<Int>() - - @get:Rule - var scenarioRule: ActivityScenarioRule<MainActivity> = - ActivityScenarioRule<MainActivity>(MainActivity::class.java) - - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - val defaultBufferSize = Rect(0, 0, 640, 480) - - @Before - fun setup() { - mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver, - "use_blast_adapter_sv", 0) - val enable = if (useBlastAdapter) 1 else 0 - Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv", - enable) - val tmpDir = instrumentation.targetContext.dataDir.toPath() - LayersTraceMonitor(tmpDir).stop() - - lateinit var surfaceReadyLatch: CountDownLatch - scenarioRule.getScenario().onActivity { - surfaceReadyLatch = it.addSurfaceView(defaultBufferSize) - } - surfaceReadyLatch.await() - } - - @After - fun teardown() { - scenarioRule.getScenario().close() - Settings.Global.putInt(instrumentation.context.contentResolver, - "use_blast_adapter_sv", mInitialUseBlastConfig) - } - - @Test - fun testSetBuffersGeometry_0x0_resetsBufferSize() { - val trace = withSFTracing(instrumentation, TRACE_FLAGS) { - scenarioRule.getScenario().onActivity { - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, - R8G8B8A8_UNORM) - it.mSurfaceProxy.ANativeWindowLock() - it.mSurfaceProxy.ANativeWindowUnlockAndPost() - it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) - } - } - - // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - } - - @Test - fun testSetBuffersGeometry_0x0_rejectsBuffer() { - val trace = withSFTracing(instrumentation, TRACE_FLAGS) { - scenarioRule.getScenario().onActivity { - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100, - R8G8B8A8_UNORM) - it.mSurfaceProxy.ANativeWindowLock() - it.mSurfaceProxy.ANativeWindowUnlockAndPost() - it.mSurfaceProxy.ANativeWindowLock() - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM) - // Submit buffer one with a different size which should be rejected - it.mSurfaceProxy.ANativeWindowUnlockAndPost() - - // submit a buffer with the default buffer size - it.mSurfaceProxy.ANativeWindowLock() - it.mSurfaceProxy.ANativeWindowUnlockAndPost() - it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */) - } - } - // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - - // Verify the next buffer is submitted with the correct size - assertThat(trace).layer("SurfaceView", 3).also { - it.hasBufferSize(defaultBufferSize) - it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */) - } - } - - @Test - fun testSetBuffersGeometry_smallerThanBuffer() { - val bufferSize = Rect(0, 0, 300, 200) - val trace = withSFTracing(instrumentation, TRACE_FLAGS) { - scenarioRule.getScenario().onActivity { - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(), - bufferSize.height(), R8G8B8A8_UNORM) - it.drawFrame() - it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) - } - } - - assertThat(trace).layer("SurfaceView", 1).also { - it.hasBufferSize(bufferSize) - it.hasLayerSize(defaultBufferSize) - it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */) - } - } - - @Test - fun testSetBuffersGeometry_largerThanBuffer() { - val bufferSize = Rect(0, 0, 3000, 2000) - val trace = withSFTracing(instrumentation, TRACE_FLAGS) { - scenarioRule.getScenario().onActivity { - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(), - bufferSize.height(), R8G8B8A8_UNORM) - it.drawFrame() - it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */) - } - } - - assertThat(trace).layer("SurfaceView", 1).also { - it.hasBufferSize(bufferSize) - it.hasLayerSize(defaultBufferSize) - it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */) - } - } - - /** Submit buffers as fast as possible and make sure they are queued */ - @Test - fun testQueueBuffers() { - val trace = withSFTracing(instrumentation, TRACE_FLAGS) { - scenarioRule.getScenario().onActivity { - it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100, - R8G8B8A8_UNORM) - for (i in 0..100) { - it.mSurfaceProxy.ANativeWindowLock() - it.mSurfaceProxy.ANativeWindowUnlockAndPost() - } - it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */) - } - } - for (frameNumber in 1..100) { - assertThat(trace).layer("SurfaceView", frameNumber.toLong()) - } - } - - companion object { - private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL - private const val R8G8B8A8_UNORM = 1 - - @JvmStatic - @Parameterized.Parameters(name = "blast={0}") - fun data(): Collection<Array<Any>> { - return listOf( - arrayOf(false), // First test: submit buffers via bufferqueue - arrayOf(true) // Second test: submit buffers via blast adapter - ) - } - } -}
\ No newline at end of file diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt new file mode 100644 index 000000000000..093c3125f253 --- /dev/null +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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 com.android.test + +import android.app.Instrumentation +import android.graphics.Point +import android.provider.Settings +import androidx.test.InstrumentationRegistry +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.rules.TestName +import org.junit.runners.Parameterized +import kotlin.properties.Delegates + +open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) { + private var mInitialBlastConfig by Delegates.notNull<Boolean>() + + val instrumentation: Instrumentation + get() = InstrumentationRegistry.getInstrumentation() + + @get:Rule + var mName = TestName() + + @Before + open fun setup() { + mInitialBlastConfig = getBlastAdapterSvEnabled() + setBlastAdapterSvEnabled(useBlastAdapter) + } + + @After + open fun teardown() { + setBlastAdapterSvEnabled(mInitialBlastConfig) + } + + private fun getBlastAdapterSvEnabled(): Boolean { + return Settings.Global.getInt(instrumentation.context.contentResolver, + "use_blast_adapter_sv", 0) != 0 + } + + private fun setBlastAdapterSvEnabled(enable: Boolean) { + Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv", + if (enable) 1 else 0) + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "blast={0}") + fun data(): Collection<Array<Any>> { + return listOf( + arrayOf(false), // First test: submit buffers via bufferqueue + arrayOf(true) // Second test: submit buffers via blast adapter + ) + } + + const val R8G8B8A8_UNORM = 1 + val defaultBufferSize = Point(640, 480) + + // system/window.h definitions + enum class ScalingMode() { + FREEZE, // = 0 + SCALE_TO_WINDOW, // =1 + SCALE_CROP, // = 2 + NO_SCALE_CROP // = 3 + } + + // system/window.h definitions + enum class Transform(val value: Int) { + /* flip source image horizontally */ + FLIP_H(1), + /* flip source image vertically */ + FLIP_V(2), + /* rotate source image 90 degrees clock-wise, and is applied after TRANSFORM_FLIP_{H|V} */ + ROT_90(4), + /* rotate source image 180 degrees */ + ROT_180(3), + /* rotate source image 270 degrees clock-wise */ + ROT_270(7), + /* transforms source by the inverse transform of the screen it is displayed onto. This + * transform is applied last */ + INVERSE_DISPLAY(0x08) + } + } +}
\ No newline at end of file diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt index abccd6cf77bd..fe9deae80407 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt @@ -16,6 +16,7 @@ package com.android.test.taskembed import android.app.Instrumentation +import android.graphics.Point import android.graphics.Rect import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry @@ -93,13 +94,15 @@ class ResizeTasksSyncTest { // verify buffer size should be changed to expected values. assertThat(trace).layer(FIRST_ACTIVITY, frame).also { - it.hasLayerSize(firstBounds) - it.hasBufferSize(firstBounds) + val firstTaskSize = Point(firstBounds.width(), firstBounds.height()) + it.hasLayerSize(firstTaskSize) + it.hasBufferSize(firstTaskSize) } assertThat(trace).layer(SECOND_ACTIVITY, frame).also { - it.hasLayerSize(secondBounds) - it.hasBufferSize(secondBounds) + val secondTaskSize = Point(secondBounds.width(), secondBounds.height()) + it.hasLayerSize(secondTaskSize) + it.hasBufferSize(secondTaskSize) } } |