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
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
index 647da2a..48031de 100644
--- a/tests/SurfaceViewBufferTests/Android.bp
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -33,6 +33,8 @@
         "kotlinx-coroutines-android",
         "flickerlib",
         "truth-prebuilt",
+        "cts-wm-util",
+        "CtsSurfaceValidatorLib",
     ],
 }
 
@@ -43,6 +45,7 @@
     ],
     shared_libs: [
         "libutils",
+        "libui",
         "libgui",
         "liblog",
         "libandroid",
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index 95885c1..c910ecd 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 0c86524..ce226fd 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 @@
     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 @@
                                     &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 @@
     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 0000000..eb16bad
--- /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 0000000..95a7fd5
--- /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 0000000..03f8c05
--- /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 0000000..eac3041
--- /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 b1e1336..ed79054 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
+        }
     }
 
-    fun addSurfaceView(size: Rect): CountDownLatch {
+    override fun getCaptureDurationMs(): Long {
+        return 30000
+    }
+
+    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 0000000..df3d30e
--- /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 0000000..996a1d3
--- /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 0000000..ae662506
--- /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 884aae41..cfbd3ac 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 0000000..cd4b385
--- /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 b48a91d..0000000
--- 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 0000000..093c312
--- /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 abccd6cf..fe9deae 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 @@
 
         // 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)
         }
     }