Merge "Update the credentials module min sdk version to 19" into androidx-main
diff --git a/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt b/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
index 108129b..514019a 100644
--- a/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
+++ b/car/app/app/api/public_plus_experimental_1.3.0-beta02.txt
@@ -841,6 +841,44 @@
 
 }
 
+package androidx.car.app.messaging.model {
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public class CarMessage {
+    method public androidx.car.app.model.CarText getBody();
+    method public long getReceivedTimeEpochMillis();
+    method public androidx.core.app.Person getSender();
+    method public boolean isRead();
+  }
+
+  public static final class CarMessage.Builder {
+    ctor public CarMessage.Builder();
+    method public androidx.car.app.messaging.model.CarMessage build();
+    method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public class ConversationItem implements androidx.car.app.model.Item {
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getId();
+    method public java.util.List<androidx.car.app.messaging.model.CarMessage!> getMessages();
+    method public androidx.car.app.model.CarText getTitle();
+    method public boolean isGroupConversation();
+  }
+
+  public static final class ConversationItem.Builder {
+    ctor public ConversationItem.Builder();
+    method public androidx.car.app.messaging.model.ConversationItem build();
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setId(String);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setMessages(java.util.List<androidx.car.app.messaging.model.CarMessage!>);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setTitle(androidx.car.app.model.CarText);
+  }
+
+}
+
 package androidx.car.app.model {
 
   @androidx.car.app.annotations.CarProtocol public final class Action {
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 108129b..514019a 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -841,6 +841,44 @@
 
 }
 
+package androidx.car.app.messaging.model {
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public class CarMessage {
+    method public androidx.car.app.model.CarText getBody();
+    method public long getReceivedTimeEpochMillis();
+    method public androidx.core.app.Person getSender();
+    method public boolean isRead();
+  }
+
+  public static final class CarMessage.Builder {
+    ctor public CarMessage.Builder();
+    method public androidx.car.app.messaging.model.CarMessage build();
+    method public androidx.car.app.messaging.model.CarMessage.Builder setBody(androidx.car.app.model.CarText);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setRead(boolean);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setReceivedTimeEpochMillis(long);
+    method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public class ConversationItem implements androidx.car.app.model.Item {
+    method public androidx.car.app.model.CarIcon? getIcon();
+    method public String getId();
+    method public java.util.List<androidx.car.app.messaging.model.CarMessage!> getMessages();
+    method public androidx.car.app.model.CarText getTitle();
+    method public boolean isGroupConversation();
+  }
+
+  public static final class ConversationItem.Builder {
+    ctor public ConversationItem.Builder();
+    method public androidx.car.app.messaging.model.ConversationItem build();
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setIcon(androidx.car.app.model.CarIcon);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setId(String);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setMessages(java.util.List<androidx.car.app.messaging.model.CarMessage!>);
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setTitle(androidx.car.app.model.CarText);
+  }
+
+}
+
 package androidx.car.app.model {
 
   @androidx.car.app.annotations.CarProtocol public final class Action {
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
new file mode 100644
index 0000000..f5bd59f
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022 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 androidx.car.app.messaging.model;
+
+import static java.util.Objects.requireNonNull;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.annotations.CarProtocol;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.CarText;
+import androidx.core.app.Person;
+
+/** Represents a single message in a {@link ConversationItem} */
+@ExperimentalCarApi
+@CarProtocol
+@RequiresCarApi(6)
+public class CarMessage {
+    @Keep
+    @NonNull
+    private final Person mSender;
+
+    @Keep
+    @NonNull
+    private final CarText mBody;
+    @Keep
+    private final long mReceivedTimeEpochMillis;
+
+    @Keep
+    private final boolean mIsRead;
+
+    CarMessage(@NonNull Builder builder) {
+        this.mSender = requireNonNull(builder.mSender);
+        this.mBody = requireNonNull(builder.mBody);
+        this.mReceivedTimeEpochMillis = builder.mReceivedTimeEpochMillis;
+        this.mIsRead = builder.mIsRead;
+    }
+
+    /** Default constructor for serialization. */
+    private CarMessage() {
+        this.mSender = new Person.Builder().setName("").build();
+        this.mBody = new CarText.Builder("").build();
+        this.mReceivedTimeEpochMillis = 0;
+        this.mIsRead = false;
+    }
+
+
+    /** Returns a {@link Person} representing the message sender */
+    @NonNull public Person getSender() {
+        return mSender;
+    }
+
+    /** Returns a {@link CarText} representing the message body */
+    @NonNull
+    public CarText getBody() {
+        return mBody;
+    }
+
+    /** Returns a {@code long} representing the message timestamp (in epoch millis) */
+    public long getReceivedTimeEpochMillis() {
+        return mReceivedTimeEpochMillis;
+    }
+
+    /** Returns a {@link boolean}, indicating whether the message has been read */
+    public boolean isRead() {
+        return mIsRead;
+    }
+
+    /** A builder for {@link CarMessage} */
+    public static final class Builder {
+        @Nullable
+        Person mSender;
+        @Nullable
+        CarText mBody;
+        long mReceivedTimeEpochMillis;
+        boolean mIsRead;
+
+        /** Sets a {@link Person} representing the message sender */
+        public @NonNull Builder setSender(@NonNull Person sender) {
+            mSender = sender;
+            return this;
+        }
+
+        /** Sets a {@link CarText} representing the message body */
+        public @NonNull Builder setBody(@NonNull CarText body) {
+            mBody = body;
+            return this;
+        }
+
+        /** Sets a {@code long} representing the message timestamp (in epoch millis) */
+        public @NonNull Builder setReceivedTimeEpochMillis(long receivedTimeEpochMillis) {
+            mReceivedTimeEpochMillis = receivedTimeEpochMillis;
+            return this;
+        }
+
+        /** Sets a {@link boolean}, indicating whether the message has been read */
+        public @NonNull Builder setRead(boolean isRead) {
+            mIsRead = isRead;
+            return this;
+        }
+
+        /** Returns a new {@link CarMessage} instance defined by this builder */
+        public @NonNull CarMessage build() {
+            return new CarMessage(this);
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
new file mode 100644
index 0000000..42eeeba
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2022 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 androidx.car.app.messaging.model;
+
+import static java.util.Objects.requireNonNull;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.annotations.CarProtocol;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.CarText;
+import androidx.car.app.model.Item;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Represents a conversation */
+@ExperimentalCarApi
+@CarProtocol
+@RequiresCarApi(6)
+public class ConversationItem implements Item {
+    @NonNull private final String mId;
+    @NonNull private final CarText mTitle;
+    @Nullable
+    private final CarIcon mIcon;
+    private final boolean mIsGroupConversation;
+    @NonNull private final List<CarMessage> mMessages;
+
+    ConversationItem(@NonNull Builder builder) {
+        this.mId = requireNonNull(builder.mId);
+        this.mTitle = requireNonNull(builder.mTitle);
+        this.mIcon = builder.mIcon;
+        this.mIsGroupConversation = builder.mIsGroupConversation;
+        this.mMessages = requireNonNull(builder.mMessages);
+    }
+
+    /** Default constructor for serialization. */
+    private ConversationItem() {
+        mId = "";
+        mTitle = new CarText.Builder("").build();
+        mIcon = null;
+        mIsGroupConversation = false;
+        mMessages = new ArrayList<>();
+    }
+
+    /**
+     * Returns a unique identifier for the conversation
+     *
+     * @see Builder#setId
+     */
+    public @NonNull String getId() {
+        return mId;
+    }
+
+    /** Returns the title of the conversation */
+    public @NonNull CarText getTitle() {
+        return mTitle;
+    }
+
+    /** Returns a {@link CarIcon} for the conversation, or {@code null} if not set */
+    public @Nullable CarIcon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns whether this conversation involves 3+ participants (a "group" conversation)
+     *
+     * @see Builder#setGroupConversation(boolean)
+     */
+    public boolean isGroupConversation() {
+        return mIsGroupConversation;
+    }
+
+    /** Returns a list of messages for this {@link ConversationItem} */
+    public @NonNull List<CarMessage> getMessages() {
+        return mMessages;
+    }
+
+    /** A builder for {@link ConversationItem} */
+    public static final class Builder {
+        @Nullable
+        String mId;
+        @Nullable
+        CarText mTitle;
+        @Nullable
+        CarIcon mIcon;
+        boolean mIsGroupConversation;
+        @Nullable
+        List<CarMessage> mMessages;
+
+        /**
+         * Specifies a unique identifier for the conversation
+         *
+         * <p> IDs may be used for a variety of purposes, including...
+         * <ul>
+         *     <li> Distinguishing new {@link ConversationItem}s from updated
+         *     {@link ConversationItem}s in the UI, when data is refreshed
+         *     <li> Identifying {@link ConversationItem}s in "mark as read" / "reply" callbacks
+         * </ul>
+         */
+        public @NonNull Builder setId(@NonNull String id) {
+            mId = id;
+            return this;
+        }
+
+        /** Sets the title of the conversation */
+        public @NonNull Builder setTitle(@NonNull CarText title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** Sets a {@link CarIcon} for the conversation */
+        public @NonNull Builder setIcon(@NonNull CarIcon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Specifies whether this conversation involves 3+ participants (a "group" conversation)
+         *
+         * <p> If unspecified, conversations are assumed to have exactly two participants (a "1:1"
+         * conversation)
+         *
+         * <p> UX presentation may differ slightly between group and 1:1 conversations. As a
+         * historical example, message readout may include sender names for group conversations, but
+         * omit them for 1:1 conversations.
+         */
+        public @NonNull Builder setGroupConversation(boolean isGroupConversation) {
+            mIsGroupConversation = isGroupConversation;
+            return this;
+        }
+
+        /** Specifies a list of messages for the conversation */
+        public @NonNull Builder setMessages(@NonNull List<CarMessage> messages) {
+            mMessages = messages;
+            return this;
+        }
+
+        /** Returns a new {@link ConversationItem} instance defined by this builder */
+        public @NonNull ConversationItem build() {
+            return new ConversationItem(this);
+        }
+    }
+}
diff --git a/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt b/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt
index 7e443a4..a8bdf7a 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt
@@ -33,6 +33,7 @@
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     fun testGlanceMatchMaterial3Colors() {
         val lightColors = mapOf(
             androidx.glance.R.color.glance_colorPrimary to
@@ -94,7 +95,7 @@
     }
 
     @Test
-    @Config(qualifiers = "night")
+    @Config(qualifiers = "night", sdk = [Build.VERSION_CODES.R])
     fun testGlanceMatchMaterial3NightColors() {
         val darkColors = mapOf(
             androidx.glance.R.color.glance_colorPrimary to
diff --git a/graphics/graphics-core/api/current.txt b/graphics/graphics-core/api/current.txt
index 1fe1a05..781c97c 100644
--- a/graphics/graphics-core/api/current.txt
+++ b/graphics/graphics-core/api/current.txt
@@ -28,7 +28,11 @@
     method public boolean awaitForever();
     method public void close();
     method public static androidx.graphics.lowlatency.SyncFenceCompat createNativeSyncFence(androidx.graphics.opengl.egl.EGLSpec egl);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public long getSignalTime();
+    method public boolean isValid();
     field public static final androidx.graphics.lowlatency.SyncFenceCompat.Companion Companion;
+    field public static final long SIGNAL_TIME_INVALID = -1L; // 0xffffffffffffffffL
+    field public static final long SIGNAL_TIME_PENDING = 9223372036854775807L; // 0x7fffffffffffffffL
   }
 
   public static final class SyncFenceCompat.Companion {
diff --git a/graphics/graphics-core/api/public_plus_experimental_current.txt b/graphics/graphics-core/api/public_plus_experimental_current.txt
index 1fe1a05..781c97c 100644
--- a/graphics/graphics-core/api/public_plus_experimental_current.txt
+++ b/graphics/graphics-core/api/public_plus_experimental_current.txt
@@ -28,7 +28,11 @@
     method public boolean awaitForever();
     method public void close();
     method public static androidx.graphics.lowlatency.SyncFenceCompat createNativeSyncFence(androidx.graphics.opengl.egl.EGLSpec egl);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public long getSignalTime();
+    method public boolean isValid();
     field public static final androidx.graphics.lowlatency.SyncFenceCompat.Companion Companion;
+    field public static final long SIGNAL_TIME_INVALID = -1L; // 0xffffffffffffffffL
+    field public static final long SIGNAL_TIME_PENDING = 9223372036854775807L; // 0x7fffffffffffffffL
   }
 
   public static final class SyncFenceCompat.Companion {
diff --git a/graphics/graphics-core/api/restricted_current.txt b/graphics/graphics-core/api/restricted_current.txt
index d2ba87f..141298c 100644
--- a/graphics/graphics-core/api/restricted_current.txt
+++ b/graphics/graphics-core/api/restricted_current.txt
@@ -28,7 +28,11 @@
     method public boolean awaitForever();
     method public void close();
     method public static androidx.graphics.lowlatency.SyncFenceCompat createNativeSyncFence(androidx.graphics.opengl.egl.EGLSpec egl);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public long getSignalTime();
+    method public boolean isValid();
     field public static final androidx.graphics.lowlatency.SyncFenceCompat.Companion Companion;
+    field public static final long SIGNAL_TIME_INVALID = -1L; // 0xffffffffffffffffL
+    field public static final long SIGNAL_TIME_PENDING = 9223372036854775807L; // 0x7fffffffffffffffL
   }
 
   public static final class SyncFenceCompat.Companion {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
index bb0bd74..246dced 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
@@ -39,7 +39,6 @@
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -279,7 +278,6 @@
     }
 
     @Test
-    @Ignore("b/244755709")
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testRenderFrontBufferSeveralTimes() {
         if (!deviceSupportsNativeAndroidFence()) {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LineRenderer.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LineRenderer.kt
new file mode 100644
index 0000000..e80f18e
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/LineRenderer.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2022 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 androidx.graphics.lowlatency
+
+import android.opengl.GLES20
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.FloatBuffer
+import org.junit.Assert.assertEquals
+
+/**
+ * OpenGL Renderer class responsible for drawing lines
+ */
+class LineRenderer {
+
+    private var mVertexShader: Int = -1
+    private var mFragmentShader: Int = -1
+    private var mGlProgram: Int = -1
+
+    private var mPositionHandle: Int = -1
+    private var mMvpMatrixHandle: Int = -1
+
+    private var mVertexBuffer: FloatBuffer? = null
+    private val mLineCoords = FloatArray(6)
+
+    fun initialize() {
+        release()
+        mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VertexShaderCode)
+        mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FragmentShaderCode)
+
+        mGlProgram = GLES20.glCreateProgram()
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+        GLES20.glAttachShader(mGlProgram, mVertexShader)
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+        GLES20.glAttachShader(mGlProgram, mFragmentShader)
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+        GLES20.glLinkProgram(mGlProgram)
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+        val bb: ByteBuffer =
+            ByteBuffer.allocateDirect( // (number of coordinate values * 4 bytes per float)
+                LineCoordsSize * 4
+            )
+        // use the device hardware's native byte order
+        bb.order(ByteOrder.nativeOrder())
+
+        // create a floating point buffer from the ByteBuffer
+        mVertexBuffer = bb.asFloatBuffer().apply {
+            put(mLineCoords)
+            position(0)
+        }
+
+        mPositionHandle = GLES20.glGetAttribLocation(mGlProgram, vPosition)
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+        mMvpMatrixHandle = GLES20.glGetUniformLocation(mGlProgram, uMVPMatrix)
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+    }
+
+    fun release() {
+        if (mVertexShader != -1) {
+            GLES20.glDeleteShader(mVertexShader)
+            mVertexShader = -1
+        }
+
+        if (mFragmentShader != -1) {
+            GLES20.glDeleteShader(mFragmentShader)
+            mFragmentShader = -1
+        }
+
+        if (mGlProgram != -1) {
+            GLES20.glDeleteProgram(mGlProgram)
+            mGlProgram = -1
+        }
+    }
+
+    fun drawLines(mvpMatrix: FloatArray, lines: FloatArray) {
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+        GLES20.glUseProgram(mGlProgram)
+
+        val buff = FloatBuffer.allocate(2)
+        GLES20.glGetFloatv(GLES20.GL_ALIASED_LINE_WIDTH_RANGE, buff)
+        GLES20.glLineWidth(100.0f)
+
+        GLES20.glEnableVertexAttribArray(mPositionHandle)
+
+        GLES20.glUniformMatrix4fv(mMvpMatrixHandle, 1, false, mvpMatrix, 0)
+
+        mVertexBuffer?.let { buffer ->
+            for (i in 0 until lines.size step 4) {
+                mLineCoords[0] = lines[i]
+                mLineCoords[1] = lines[i + 1]
+                mLineCoords[2] = 0f
+                mLineCoords[3] = lines[i + 2]
+                mLineCoords[4] = lines[i + 3]
+                mLineCoords[5] = 0f
+                buffer.put(mLineCoords)
+                buffer.position(0)
+            }
+
+            // Prepare the triangle coordinate data
+            GLES20.glVertexAttribPointer(
+                mPositionHandle, CoordsPerVertex,
+                GLES20.GL_FLOAT, false,
+                VertexStride, buffer
+            )
+            GLES20.glDrawArrays(GLES20.GL_LINES, 0, VertexCount)
+
+            GLES20.glDisableVertexAttribArray(mPositionHandle)
+            assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+        }
+    }
+
+    companion object {
+
+        const val CoordsPerVertex = 3
+        const val LineCoordsSize = 6
+        private val VertexCount: Int = LineCoordsSize / CoordsPerVertex
+        private val VertexStride: Int = CoordsPerVertex * 4 // 4 bytes per vertex
+
+        private const val uMVPMatrix = "uMVPMatrix"
+        private const val vPosition = "vPosition"
+        private const val VertexShaderCode =
+            """
+                uniform mat4 $uMVPMatrix;
+                attribute vec4 $vPosition;
+                void main() { // the matrix must be included as a modifier of gl_Position
+                  gl_Position = $uMVPMatrix * $vPosition;
+                }
+            """
+
+        private const val FragmentShaderCode =
+            """
+                precision highp float;
+
+                void main() {
+                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+                }
+            """
+
+        fun loadShader(type: Int, shaderCode: String?): Int {
+            val shader = GLES20.glCreateShader(type)
+
+            GLES20.glShaderSource(shader, shaderCode)
+            GLES20.glCompileShader(shader)
+
+            return shader
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SyncStrategyTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SyncStrategyTest.kt
new file mode 100644
index 0000000..d836469
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SyncStrategyTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2022 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 androidx.graphics.lowlatency
+
+import android.opengl.EGL14
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.graphics.opengl.egl.EGLConfigAttributes
+import androidx.graphics.opengl.egl.EGLManager
+import androidx.graphics.opengl.egl.EGLSpec
+import androidx.graphics.opengl.egl.EGLVersion
+import androidx.graphics.opengl.egl.supportsNativeAndroidFence
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SyncStrategyTest {
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun testSyncStrategy_Always() {
+        val egl = createAndSetupEGLManager(EGLSpec.V14)
+        if (egl.supportsNativeAndroidFence()) {
+            val strategy = SyncStrategy.ALWAYS
+            val fence = strategy.createSyncFence(egl.eglSpec)
+            assertTrue(fence != null)
+            fence?.close()
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    @Test
+    fun testSyncStrategy_onFirstShow_FrontBufferUsageOff_Invisible() {
+        val egl = createAndSetupEGLManager(EGLSpec.V14)
+        if (egl.supportsNativeAndroidFence()) {
+            val strategy = FrontBufferSyncStrategy(false)
+            val fence = strategy.createSyncFence(EGLSpec.V14)
+            assertTrue(fence != null)
+            fence?.close()
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    @Test
+    fun testSyncStrategy_onFirstShow_FrontBufferUsageOff_Visible() {
+        val egl = createAndSetupEGLManager(EGLSpec.V14)
+        if (egl.supportsNativeAndroidFence()) {
+            val strategy = FrontBufferSyncStrategy(false)
+            strategy.setVisible(true)
+            val fence = strategy.createSyncFence(EGLSpec.V14)
+            assertTrue(fence == null)
+            fence?.close()
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    @Test
+    fun testSyncStrategy_onFirstShow_FrontBufferUsageOn_Invisible() {
+        val egl = createAndSetupEGLManager(EGLSpec.V14)
+        if (egl.supportsNativeAndroidFence()) {
+            val strategy = FrontBufferSyncStrategy(true)
+            val fence = strategy.createSyncFence(egl.eglSpec)
+            assertTrue(fence != null)
+            fence?.close()
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    @Test
+    fun testSyncStrategy_onFirstShow_FrontBufferUsageOn_Visible() {
+        val egl = createAndSetupEGLManager(EGLSpec.V14)
+        if (egl.supportsNativeAndroidFence()) {
+            val strategy = FrontBufferSyncStrategy(true)
+            strategy.setVisible(true)
+            val fence = strategy.createSyncFence(EGLSpec.V14)
+            assertTrue(fence == null)
+            fence?.close()
+        }
+    }
+
+    // Helper method to create and initialize an EGLManager
+    fun createAndSetupEGLManager(eglSpec: EGLSpec = EGLSpec.V14): EGLManager {
+        val egl = EGLManager(eglSpec)
+        Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
+        Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
+
+        egl.initialize()
+
+        val config = egl.loadConfig(EGLConfigAttributes.RGBA_8888)
+        if (config == null) {
+            Assert.fail("Config 888 should be supported")
+        }
+
+        egl.createContext(config!!)
+        return egl
+    }
+
+    // Helper method to release EGLManager
+    fun releaseEGLManager(egl: EGLManager) {
+        egl.release()
+        Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
+        Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
index 7831516..b89bbad0 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
@@ -27,6 +27,7 @@
 import android.opengl.EGL14
 import android.opengl.EGLSurface
 import android.opengl.GLES20
+import android.opengl.Matrix
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
@@ -37,6 +38,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.WorkerThread
 import androidx.graphics.lowlatency.HardwareBufferRenderer
+import androidx.graphics.lowlatency.LineRenderer
 import androidx.graphics.lowlatency.RenderBuffer
 import androidx.graphics.lowlatency.SyncFenceCompat
 import androidx.graphics.opengl.egl.EGLManager
@@ -63,7 +65,6 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class GLRendererTest {
-
     @Test
     fun testStartAfterStop() {
         with(GLRenderer()) {
@@ -812,6 +813,7 @@
         val teardownLatch = CountDownLatch(1)
         val glRenderer = GLRenderer().apply { start() }
         var renderBuffer: RenderBuffer? = null
+        var status: Boolean? = false
 
         val callbacks = object : HardwareBufferRenderer.RenderCallbacks {
             override fun obtainRenderBuffer(egl: EGLSpec): RenderBuffer =
@@ -832,7 +834,11 @@
                 GLES20.glFlush()
             }
 
-            override fun onDrawComplete(renderBuffer: RenderBuffer) {
+            override fun onDrawComplete(
+                renderBuffer: RenderBuffer,
+                syncFenceCompat: SyncFenceCompat?
+            ) {
+                status = syncFenceCompat?.await(3000)
                 renderLatch.countDown()
             }
         }
@@ -846,6 +852,11 @@
         var hardwareBuffer: HardwareBuffer? = null
         try {
             assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            assert(status != null)
+            status?.let {
+                assertTrue(it)
+            }
+
             hardwareBuffer = renderBuffer?.hardwareBuffer
             if (hardwareBuffer != null) {
                 val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
@@ -869,6 +880,114 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    fun testHardwareBufferRendererWithSyncFence() {
+        if (!deviceSupportsNativeAndroidFence()) {
+            // If the Android device does not support the corresponding
+            // EGL Extensions to obtain native Android fence objects from EGLSync
+            // instances then skip this test as we cannot guarantee consistency
+            // for front buffered rendering
+            return
+        }
+
+        val width = 10
+        val height = 10
+        val renderLatch = CountDownLatch(1)
+        val teardownLatch = CountDownLatch(1)
+
+        val glRenderer = GLRenderer().apply { start() }
+        var startTime = Long.MAX_VALUE
+        var signalTime = 0L
+
+        val renderer =
+            object : HardwareBufferRenderer.RenderCallbacks, GLRenderer.EGLContextCallback {
+                private val mMVPMatrix = FloatArray(16)
+                private val mLines = FloatArray(4)
+                private val mLineRenderer = LineRenderer()
+                var mRenderBuffer: RenderBuffer? = null
+
+                @WorkerThread
+                override fun onEGLContextCreated(eglManager: EGLManager) {
+                    mLineRenderer.initialize()
+                }
+
+                @WorkerThread
+                override fun onEGLContextDestroyed(eglManager: EGLManager) {
+                    mLineRenderer.release()
+                }
+
+                @WorkerThread
+                override fun obtainRenderBuffer(egl: EGLSpec): RenderBuffer {
+                    return if (mRenderBuffer != null) {
+                        mRenderBuffer!!
+                    } else {
+                        RenderBuffer(
+                            egl,
+                            HardwareBuffer.create(
+                                width,
+                                height,
+                                HardwareBuffer.RGBA_8888,
+                                1,
+                                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                            )
+                        ).also { mRenderBuffer = it }
+                    }
+                }
+
+                @WorkerThread
+                override fun onDraw(eglManager: EGLManager) {
+                    startTime = System.nanoTime()
+                    GLES20.glViewport(0, 0, width, height)
+                    assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+                    Matrix.orthoM(mMVPMatrix, 0, 0f, width.toFloat(), 0f, height.toFloat(), -1f, 1f)
+                    mLines[0] = 0f
+                    mLines[1] = 0f
+                    mLines[2] = 5f
+                    mLines[3] = 5f
+                    mLineRenderer.drawLines(mMVPMatrix, mLines)
+                    assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+                }
+
+                @WorkerThread
+                override fun onDrawComplete(
+                    renderBuffer: RenderBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    assertNotNull(syncFenceCompat)
+                    assertTrue(syncFenceCompat!!.isValid())
+
+                    assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+                    assertTrue(syncFenceCompat.await(3000))
+                    signalTime = syncFenceCompat.getSignalTime()
+
+                    renderLatch.countDown()
+                    assertTrue(syncFenceCompat.getSignalTime() < System.nanoTime())
+                    assertTrue(syncFenceCompat.getSignalTime() > startTime)
+                }
+            }
+
+        glRenderer.registerEGLContextCallback(renderer)
+        val hwBufferRenderer = HardwareBufferRenderer(renderer)
+        val renderTarget =
+            glRenderer.createRenderTarget(width, height, hwBufferRenderer)
+
+        renderTarget.requestRender()
+        assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError())
+
+        try {
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            assertTrue(startTime < signalTime)
+            assertTrue(signalTime < System.nanoTime())
+        } finally {
+            glRenderer.stop(true) {
+                teardownLatch.countDown()
+            }
+            assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
     /**
      * Helper method to create a GLTestActivity instance and progress it through the Activity
      * lifecycle to the resumed state so we can issue rendering commands into the corresponding
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncFenceCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncFenceCompatTest.kt
index cac758d..60c06e7 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncFenceCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncFenceCompatTest.kt
@@ -26,7 +26,9 @@
 import androidx.graphics.opengl.egl.EGLSpec
 import androidx.graphics.opengl.egl.EGLVersion
 import androidx.graphics.opengl.egl.supportsNativeAndroidFence
+import androidx.hardware.SyncFence
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -36,26 +38,28 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
-@RequiresApi(Build.VERSION_CODES.KITKAT)
+@RequiresApi(Build.VERSION_CODES.O)
 class SyncFenceCompatTest {
     @Test
-    fun testRenderFenceCreate() {
+    fun testSyncFenceCompat_Create() {
         testEglManager {
             initializeWithDefaultConfig()
             if (supportsNativeAndroidFence()) {
                 val syncFenceCompat = SyncFenceCompat.createNativeSyncFence(this.eglSpec)
+                assert(syncFenceCompat.isValid())
                 syncFenceCompat.close()
             }
         }
     }
 
     @Test
-    fun testRenderFenceAwait() {
+    fun testSyncFenceCompat_Await() {
         testEglManager {
             initializeWithDefaultConfig()
             if (supportsNativeAndroidFence()) {
 
                 val syncFenceCompat = SyncFenceCompat.createNativeSyncFence(this.eglSpec)
+                assert(syncFenceCompat.isValid())
                 GLES20.glFlush()
                 assertTrue(syncFenceCompat.await(1000))
 
@@ -65,13 +69,12 @@
     }
 
     @Test
-    fun testRenderFenceAwaitForever() {
+    fun testSyncFenceCompat_AwaitForever() {
         testEglManager {
             initializeWithDefaultConfig()
             if (supportsNativeAndroidFence()) {
-
                 val syncFenceCompat = SyncFenceCompat.createNativeSyncFence(this.eglSpec)
-                GLES20.glFlush()
+                assert(syncFenceCompat.isValid())
                 assertTrue(syncFenceCompat.awaitForever())
 
                 syncFenceCompat.close()
@@ -79,6 +82,26 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun testSyncFenceCompat_SignalTime() {
+        testEglManager {
+            initializeWithDefaultConfig()
+            if (supportsNativeAndroidFence()) {
+                val start = System.nanoTime()
+                val syncFenceCompat = SyncFenceCompat.createNativeSyncFence(this.eglSpec)
+                assertTrue(syncFenceCompat.isValid())
+                assertTrue(syncFenceCompat.getSignalTime() != SyncFence.SIGNAL_TIME_INVALID)
+                assertTrue(syncFenceCompat.awaitForever())
+
+                assertTrue(syncFenceCompat.getSignalTime() > start)
+                assertTrue(syncFenceCompat.getSignalTime() != SyncFenceCompat.SIGNAL_TIME_PENDING)
+
+                syncFenceCompat.close()
+            }
+        }
+    }
+
     // Helper method used in testing to initialize EGL and default
     // EGLConfig to the ARGB8888 configuration
     private fun EGLManager.initializeWithDefaultConfig() {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
index 8f7fec9..c4dd258 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
@@ -295,8 +295,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -343,16 +343,16 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
 
                         val buffer2 =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.GREEN
                             )
                         assertNotNull(buffer2)
@@ -387,6 +387,114 @@
     }
 
     @Test
+    fun testTransactionSetBuffer_nullFence() {
+        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
+            .moveToState(
+                Lifecycle.State.CREATED
+            ).onActivity {
+                val callback = object : SurfaceHolderCallback() {
+                    override fun surfaceCreated(sh: SurfaceHolder) {
+                        val scCompat = SurfaceControlCompat
+                            .Builder()
+                            .setParent(it.getSurfaceView())
+                            .setName("SurfaceControlCompatTest")
+                            .build()
+
+                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                        val buffer =
+                            SurfaceControlUtils.getSolidBuffer(
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                                Color.BLUE
+                            )
+                        assertNotNull(buffer)
+
+                        SurfaceControlCompat.Transaction()
+                            .setBuffer(scCompat, buffer, null)
+                            .setVisibility(
+                                scCompat,
+                                true
+                            ).commit()
+                    }
+                }
+
+                it.addSurface(it.mSurfaceView, callback)
+            }
+
+        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+            SurfaceControlUtils.validateOutput { bitmap ->
+                val coord = intArrayOf(0, 0)
+                it.mSurfaceView.getLocationOnScreen(coord)
+                Color.RED == bitmap.getPixel(coord[0], coord[1])
+            }
+        }
+    }
+
+    @Test
+    fun testTransactionSetBuffer_simpleFence() {
+        var eglManager: EGLManager? = null
+        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
+            .moveToState(
+                Lifecycle.State.CREATED
+            ).onActivity {
+                val manager = EGLManager().apply {
+                    initialize()
+                    val config = loadConfig(EGLConfigAttributes.RGBA_8888)
+                    if (config == null) {
+                        fail("Config 8888 should be supported")
+                    }
+                    createContext(config!!)
+                }
+                eglManager = manager
+
+                val callback = object : SurfaceHolderCallback() {
+                    override fun surfaceCreated(sh: SurfaceHolder) {
+                        val scCompat = SurfaceControlCompat
+                            .Builder()
+                            .setParent(it.getSurfaceView())
+                            .setName("SurfaceControlCompatTest")
+                            .build()
+
+                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                        val buffer =
+                            SurfaceControlUtils.getSolidBuffer(
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                                Color.BLUE
+                            )
+                        assertNotNull(buffer)
+
+                        val fence = if (manager.supportsNativeAndroidFence()) {
+                            SyncFenceCompat.createNativeSyncFence(manager.eglSpec)
+                        } else {
+                            null
+                        }
+                        SurfaceControlCompat.Transaction()
+                            .setBuffer(scCompat, buffer, fence)
+                            .setVisibility(
+                                scCompat,
+                                true
+                            ).commit()
+                    }
+                }
+
+                it.addSurface(it.mSurfaceView, callback)
+            }
+
+        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+            try {
+                SurfaceControlUtils.validateOutput { bitmap ->
+                    val coord = intArrayOf(0, 0)
+                    it.mSurfaceView.getLocationOnScreen(coord)
+                    Color.RED == bitmap.getPixel(coord[0], coord[1])
+                }
+            } finally {
+                eglManager?.release()
+            }
+        }
+    }
+
+    @Test
     fun testTransactionSetBuffer_nullCallback() {
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(
@@ -403,8 +511,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -447,8 +555,8 @@
 
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.GREEN
                             )
                         assertNotNull(buffer)
@@ -456,8 +564,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer2 =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer2)
@@ -506,24 +614,24 @@
 
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.GREEN
                             )
                         assertNotNull(buffer)
 
                         val buffer2 =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.GREEN
                             )
                         assertNotNull(buffer2)
 
                         val buffer3 =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer3)
@@ -562,75 +670,6 @@
     }
 
     @Test
-    fun testTransactionSetBuffer_withSyncFence() {
-        val releaseLatch = CountDownLatch(1)
-        val egl = createAndSetupEGLManager(EGLSpec.V14)
-        if (egl.supportsNativeAndroidFence()) {
-            val syncFenceCompat = SyncFenceCompat.createNativeSyncFence(egl.eglSpec)
-            val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
-                .moveToState(
-                    Lifecycle.State.CREATED
-                ).onActivity {
-                    val callback = object : SurfaceHolderCallback() {
-                        override fun surfaceCreated(sh: SurfaceHolder) {
-                            val scCompat = SurfaceControlCompat
-                                .Builder()
-                                .setParent(it.getSurfaceView())
-                                .setName("SurfaceControlCompatTest")
-                                .build()
-
-                            val buffer =
-                                SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
-                                    Color.GREEN
-                                )
-                            assertNotNull(buffer)
-
-                            // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
-                            val buffer2 =
-                                SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
-                                    Color.BLUE
-                                )
-                            assertNotNull(buffer2)
-
-                            SurfaceControlCompat.Transaction()
-                                .setBuffer(
-                                    scCompat,
-                                    buffer,
-                                    syncFenceCompat,
-                                ) {
-                                    releaseLatch.countDown()
-                                }
-                                .setVisibility(scCompat, true)
-                                .commit()
-                            SurfaceControlCompat.Transaction()
-                                .setBuffer(scCompat, buffer2)
-                                .setVisibility(scCompat, true)
-                                .commit()
-                        }
-                    }
-
-                    it.addSurface(it.mSurfaceView, callback)
-                }
-
-            scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
-                assertTrue(releaseLatch.await(3000, TimeUnit.MILLISECONDS))
-                assertTrue(syncFenceCompat.await(3000))
-                SurfaceControlUtils.validateOutput { bitmap ->
-                    val coord = intArrayOf(0, 0)
-                    it.mSurfaceView.getLocationOnScreen(coord)
-                    Color.RED == bitmap.getPixel(coord[0], coord[1])
-                }
-
-                releaseEGLManager(egl)
-            }
-        }
-    }
-
-    @Test
     fun testTransactionSetVisibility_show() {
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(
@@ -647,8 +686,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -691,8 +730,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -743,8 +782,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -753,8 +792,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -800,8 +839,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -810,8 +849,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -857,8 +896,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -867,8 +906,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -907,13 +946,18 @@
                         SurfaceControlCompat.Transaction()
                             .setDamageRegion(
                                 scCompat,
-                                Region(0, 0, it.DEFAULT_WIDTH, it.DEFAULT_HEIGHT)
+                                Region(
+                                    0,
+                                    0,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+                                )
                             )
                             .setBuffer(
                                 scCompat,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -957,8 +1001,8 @@
                             .setBuffer(
                                 scCompat,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -995,8 +1039,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
 
@@ -1039,8 +1083,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlCompat.Transaction()
@@ -1082,8 +1126,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlCompat.Transaction()
@@ -1141,8 +1185,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlCompat.Transaction()
@@ -1182,8 +1226,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
 
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
index 2bafbee..80bfb12 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
@@ -315,8 +315,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -372,16 +372,16 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
 
                         val buffer2 =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.GREEN
                             )
                         assertNotNull(buffer2)
@@ -433,8 +433,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -480,8 +480,8 @@
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer =
                             SurfaceControlUtils.getSolidBuffer(
-                                it.DEFAULT_WIDTH,
-                                it.DEFAULT_HEIGHT,
+                                SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                 Color.BLUE
                             )
                         assertNotNull(buffer)
@@ -536,8 +536,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -545,8 +545,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -594,8 +594,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -603,8 +603,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -652,8 +652,8 @@
                             .setBuffer(
                                 scCompat1,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -661,8 +661,8 @@
                             .setBuffer(
                                 scCompat2,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.GREEN
                                 )
                             )
@@ -703,13 +703,18 @@
                             .addTransactionCompletedListener(listener)
                             .setDamageRegion(
                                 scCompat,
-                                Region(0, 0, it.DEFAULT_WIDTH, it.DEFAULT_HEIGHT)
+                                Region(
+                                    0,
+                                    0,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+                                )
                             )
                             .setBuffer(
                                 scCompat,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -755,8 +760,8 @@
                             .setBuffer(
                                 scCompat,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -798,8 +803,8 @@
                             .setBuffer(
                                 scCompat,
                                 SurfaceControlUtils.getSolidBuffer(
-                                    it.DEFAULT_WIDTH,
-                                    it.DEFAULT_HEIGHT,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                                    SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                                     Color.BLUE
                                 )
                             )
@@ -838,8 +843,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlWrapper.Transaction()
@@ -883,8 +888,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlWrapper.Transaction()
@@ -929,8 +934,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlWrapper.Transaction()
@@ -991,8 +996,8 @@
 
                         // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
                         val buffer = SurfaceControlUtils.getSolidBuffer(
-                            it.DEFAULT_WIDTH,
-                            it.DEFAULT_HEIGHT,
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
                             Color.BLUE
                         )
                         SurfaceControlWrapper.Transaction()
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTestActivity.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTestActivity.kt
index 962cce7..b99dad2 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTestActivity.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTestActivity.kt
@@ -27,8 +27,10 @@
     lateinit var mSurfaceView: SurfaceView
     lateinit var mFrameLayout: FrameLayout
     lateinit var mLayoutParams: FrameLayout.LayoutParams
-    var DEFAULT_WIDTH = 100
-    var DEFAULT_HEIGHT = 100
+    companion object {
+        val DEFAULT_WIDTH = 100
+        val DEFAULT_HEIGHT = 100
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
index e623c73..1089720 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
@@ -71,7 +71,31 @@
      * be synchronized with the [SurfaceControlCompat.Transaction] to show/hide visibility of the
      * front buffered layer as well as updating double buffered layers
      */
-    private val mCallback = callback
+    private val mCallback = object : Callback<T> by callback {
+        @WorkerThread
+        override fun onDoubleBufferedLayerRenderComplete(
+            frontBufferedLayerSurfaceControl: SurfaceControlCompat,
+            transaction: SurfaceControlCompat.Transaction
+        ) {
+            mFrontBufferSyncStrategy.setVisible(false)
+            callback.onDoubleBufferedLayerRenderComplete(
+                frontBufferedLayerSurfaceControl,
+                transaction
+            )
+        }
+
+        @WorkerThread
+        override fun onFrontBufferedLayerRenderComplete(
+            frontBufferedLayerSurfaceControl: SurfaceControlCompat,
+            transaction: SurfaceControlCompat.Transaction
+        ) {
+            mFrontBufferSyncStrategy.setVisible(true)
+            callback.onFrontBufferedLayerRenderComplete(
+                frontBufferedLayerSurfaceControl,
+                transaction
+            )
+        }
+    }
 
     /**
      * [GLRenderer.EGLContextCallback]s used to release the corresponding [RenderBufferPool]
@@ -157,6 +181,12 @@
     private var mFrontBufferedLayerSurfaceControl: SurfaceControlCompat? = null
 
     /**
+     * [FrontBufferSyncStrategy] used for [HardwareBufferRenderer] to conditionally decide
+     * when to create a [SyncFenceCompat] for transaction calls.
+     */
+    private val mFrontBufferSyncStrategy: FrontBufferSyncStrategy
+
+    /**
      * Width of the layers to render. Only if the size changes to we re-initialize the internal
      * state of the [GLFrontBufferedRenderer]
      */
@@ -200,6 +230,17 @@
      */
     private var mIsReleased = false
 
+    /**
+     * Cached value to store what [HardwareBuffer] usage flags are supported on the device.
+     */
+    private val mHardwareBufferUsageFlags: Long
+
+    /**
+     * Flag to determine if [HardwareBuffer.USAGE_FRONT_BUFFER] is supported or not on
+     * the device
+     */
+    private val mSupportsFrontBufferUsage: Boolean
+
     init {
         mParentRenderLayer.setParentLayerCallbacks(mParentLayerCallback)
         val renderer = if (glRenderer == null) {
@@ -216,6 +257,13 @@
         mDoubleBufferedLayerRenderTarget =
             mParentRenderLayer.createRenderTarget(renderer, mCallback)
         mGLRenderer = renderer
+
+        mHardwareBufferUsageFlags = obtainHardwareBufferUsageFlags()
+
+        mSupportsFrontBufferUsage =
+            (mHardwareBufferUsageFlags and HardwareBuffer.USAGE_FRONT_BUFFER) != 0L
+
+        mFrontBufferSyncStrategy = FrontBufferSyncStrategy(mSupportsFrontBufferUsage)
     }
 
     internal fun update(width: Int, height: Int) {
@@ -233,7 +281,7 @@
                 width,
                 height,
                 format = HardwareBuffer.RGBA_8888,
-                usage = obtainHardwareBufferUsageFlags(),
+                usage = mHardwareBufferUsageFlags,
                 maxPoolSize = 5
             )
 
@@ -289,8 +337,10 @@
             mParentBufferParamQueue.add(param)
             mFrontBufferedRenderTarget?.requestRender()
         } else {
-            Log.w(TAG, "Attempt to render to front buffered layer when " +
-                "GLFrontBufferedRenderer has been released")
+            Log.w(
+                TAG, "Attempt to render to front buffered layer when " +
+                    "GLFrontBufferedRenderer has been released"
+            )
         }
     }
 
@@ -318,8 +368,10 @@
             mDoubleBufferedLayerRenderTarget?.requestRender()
             mFrontBufferedLayerRenderer?.clear()
         } else {
-            Log.w(TAG, "Attempt to render to the double buffered layer when " +
-                "GLFrontBufferedRenderer has been released")
+            Log.w(
+                TAG, "Attempt to render to the double buffered layer when " +
+                    "GLFrontBufferedRenderer has been released"
+            )
         }
     }
 
@@ -393,58 +445,68 @@
 
     private fun createFrontBufferedLayerRenderer(
         frontBufferedLayerSurfaceControl: SurfaceControlCompat
-    ) = HardwareBufferRenderer(
-        object : HardwareBufferRenderer.RenderCallbacks {
+    ): HardwareBufferRenderer {
+        return HardwareBufferRenderer(
+            object : HardwareBufferRenderer.RenderCallbacks {
 
-            @WorkerThread
-            override fun obtainRenderBuffer(egl: EGLSpec): RenderBuffer {
-                var buffer = mFrontLayerBuffer
-                if (buffer == null) {
-                    // Allocate and persist a RenderBuffer instance across frames
-                    buffer = mBufferPool?.obtain(egl).also { mFrontLayerBuffer = it }
-                       ?: throw IllegalArgumentException("Unable to obtain RenderBuffer")
+                @WorkerThread
+                override fun obtainRenderBuffer(egl: EGLSpec): RenderBuffer {
+                    var buffer = mFrontLayerBuffer
+                    if (buffer == null) {
+                        // Allocate and persist a RenderBuffer instance across frames
+                        buffer = mBufferPool?.obtain(egl).also { mFrontLayerBuffer = it }
+                            ?: throw IllegalArgumentException("Unable to obtain RenderBuffer")
+                    }
+                    return buffer
                 }
-                return buffer
-            }
 
-            @WorkerThread
-            override fun onDraw(eglManager: EGLManager) {
-                try {
-                    // Explicitly call remove in order to delineate between scenarios where
-                    // no parameters are provided and the consumer explicitly supports nullable
-                    // parameters.
-                    // If poll was used instead, we would not be able to determine if the nullable
-                    // parameter was because there were no items in the queue or the consumer
-                    // explicitly provided null as a placeholder
-                    mCallback.onDrawFrontBufferedLayer(eglManager, mFrontBufferQueueParams.remove())
-                } catch (_: NoSuchElementException) {
-                    // Skip rendering if we have been told to render but we do not have parameters
-                    // Because the call to render to the front buffer takes in a parameter we should
-                    // not run into this scenario.
+                @WorkerThread
+                override fun onDraw(eglManager: EGLManager) {
+                    try {
+                        // Explicitly call remove in order to delineate between scenarios where
+                        // no parameters are provided and the consumer explicitly supports nullable
+                        // parameters.
+                        // If poll was used instead, we would not be able to determine if the nullable
+                        // parameter was because there were no items in the queue or the consumer
+                        // explicitly provided null as a placeholder
+                        mCallback.onDrawFrontBufferedLayer(
+                            eglManager,
+                            mFrontBufferQueueParams.remove()
+                        )
+                    } catch (_: NoSuchElementException) {
+                        // Skip rendering if we have been told to render but we do not have parameters
+                        // Because the call to render to the front buffer takes in a parameter we should
+                        // not run into this scenario.
+                    }
                 }
-            }
 
-            @WorkerThread
-            override fun onDrawComplete(renderBuffer: RenderBuffer) {
-                val transaction = SurfaceControlCompat.Transaction()
-                    // Make this layer the top most layer
-                    .setLayer(frontBufferedLayerSurfaceControl, Integer.MAX_VALUE)
-                    .setBuffer(
-                        frontBufferedLayerSurfaceControl,
-                        renderBuffer.hardwareBuffer,
-                        null
+                @WorkerThread
+                override fun onDrawComplete(
+                    renderBuffer: RenderBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    val transaction = SurfaceControlCompat.Transaction()
+                        // Make this layer the top most layer
+                        .setLayer(frontBufferedLayerSurfaceControl, Integer.MAX_VALUE)
+                        .setBuffer(
+                            frontBufferedLayerSurfaceControl,
+                            renderBuffer.hardwareBuffer,
+                            syncFenceCompat
+                        )
+                        .setVisibility(frontBufferedLayerSurfaceControl, true)
+                    mParentRenderLayer.buildReparentTransaction(
+                        frontBufferedLayerSurfaceControl, transaction
                     )
-                    .setVisibility(frontBufferedLayerSurfaceControl, true)
-                mParentRenderLayer.buildReparentTransaction(
-                    frontBufferedLayerSurfaceControl, transaction)
-                mCallback.onFrontBufferedLayerRenderComplete(
-                    frontBufferedLayerSurfaceControl,
-                    transaction
-                )
-                transaction.commit()
-            }
-        }
-    )
+                    mCallback.onFrontBufferedLayerRenderComplete(
+                        frontBufferedLayerSurfaceControl,
+                        transaction
+                    )
+                    transaction.commit()
+                }
+            },
+            mFrontBufferSyncStrategy
+        )
+    }
 
     private fun clearParamQueues() {
         mFrontBufferQueueParams.clear()
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/HardwareBufferRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/HardwareBufferRenderer.kt
index 288c3e6..14fb6b9 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/HardwareBufferRenderer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/HardwareBufferRenderer.kt
@@ -34,7 +34,8 @@
  */
 @RequiresApi(Build.VERSION_CODES.O)
 internal class HardwareBufferRenderer(
-    private val hardwareBufferRendererCallbacks: RenderCallbacks
+    private val hardwareBufferRendererCallbacks: RenderCallbacks,
+    private val syncStrategy: SyncStrategy = SyncStrategy.ALWAYS
 ) : GLRenderer.RenderCallback {
 
     private val mClear = AtomicBoolean(false)
@@ -63,13 +64,12 @@
             } else {
                 hardwareBufferRendererCallbacks.onDraw(eglManager)
             }
-            GLES20.glFlush()
-            syncFenceCompat = egl.createNativeSyncFence()
 
-            syncFenceCompat.awaitForever()
+            syncFenceCompat = syncStrategy.createSyncFence(egl)
+
             // At this point the HardwareBuffer has the contents of the GL rendering
             // Create a surface Control transaction to dispatch this request
-            hardwareBufferRendererCallbacks.onDrawComplete(buffer)
+            hardwareBufferRendererCallbacks.onDrawComplete(buffer, syncFenceCompat)
         } finally {
             syncFenceCompat?.close()
         }
@@ -98,6 +98,71 @@
          * Callback when [onDraw] is complete and the contents of the draw
          * are reflected in the corresponding [HardwareBuffer]
          */
-        fun onDrawComplete(renderBuffer: RenderBuffer)
+        fun onDrawComplete(renderBuffer: RenderBuffer, syncFenceCompat: SyncFenceCompat?)
+    }
+}
+
+/**
+ * A strategy class for deciding how to utilize [SyncFenceCompat] within
+ * [HardwareBufferRenderer.RenderCallbacks]. SyncStrategy provides default strategies for
+ * usage:
+ *
+ * [SyncStrategy.ALWAYS] will always create a [SyncFenceCompat] to pass into the render
+ * callbacks for [HardwareBufferRenderer]
+ */
+internal interface SyncStrategy {
+    /**
+     * Conditionally generates a [SyncFenceCompat] based upon implementation.
+     *
+     * @param eglSpec an [EGLSpec] object to dictate the version of EGL and make EGL calls.
+     */
+    fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat?
+
+    companion object {
+        /**
+         * [SyncStrategy] that will always create a [SyncFenceCompat] object
+         */
+        @JvmField
+        val ALWAYS = object : SyncStrategy {
+            override fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? {
+                return eglSpec.createNativeSyncFence()
+            }
+        }
+    }
+}
+
+/**
+ * [SyncStrategy] implementation that optimizes for front buffered rendering use cases.
+ * More specifically this attempts to avoid unnecessary synchronization overhead
+ * wherever possible.
+ *
+ * This will always provide a fence if the corresponding layer transitions from
+ * an invisible to a visible state. If the layer is already visible and front
+ * buffer usage flags are support on the device, then no fence is provided. If this
+ * flag is not supported, then a fence is created and "peeked" to ensure contents
+ * are flushed to the single buffer.
+ */
+internal class FrontBufferSyncStrategy(
+    private val supportsFrontBufferUsage: Boolean
+) : SyncStrategy {
+    private var mFrontBufferVisible: Boolean = false
+
+    fun isVisible(): Boolean = mFrontBufferVisible
+
+    fun setVisible(visibility: Boolean) {
+        mFrontBufferVisible = visibility
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    override fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? {
+        return if (!isVisible()) {
+            eglSpec.createNativeSyncFence()
+        } else if (supportsFrontBufferUsage) {
+            return null
+        } else {
+            val fence = eglSpec.createNativeSyncFence()
+            fence.close()
+            return null
+        }
     }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
index 460209b..22fd3f29 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
@@ -81,14 +81,17 @@
                 }
 
                 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-                override fun onDrawComplete(renderBuffer: RenderBuffer) {
+                override fun onDrawComplete(
+                    renderBuffer: RenderBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
                     val frontBufferedLayerSurfaceControl = mLayerCallback
-                            ?.getFrontBufferedLayerSurfaceControl()
+                        ?.getFrontBufferedLayerSurfaceControl()
                     val sc = mParentSurfaceControl
-                        // At this point the parentSurfaceControl should already be created
-                        // in the surfaceChanged callback, however, if for whatever reason this
-                        // was not the case, create the double buffered SurfaceControl now and cache
-                        // it
+                    // At this point the parentSurfaceControl should already be created
+                    // in the surfaceChanged callback, however, if for whatever reason this
+                    // was not the case, create the double buffered SurfaceControl now and cache
+                    // it
                         ?: createDoubleBufferedSurfaceControl().also {
                             mParentSurfaceControl = it
                         }
@@ -96,7 +99,7 @@
                         val transaction = SurfaceControlCompat.Transaction()
                             .setVisibility(frontBufferedLayerSurfaceControl, false)
                             .setVisibility(sc, true)
-                            .setBuffer(sc, renderBuffer.hardwareBuffer) {
+                            .setBuffer(sc, renderBuffer.hardwareBuffer, syncFenceCompat) {
                                 mLayerCallback?.getRenderBufferPool()?.release(renderBuffer)
                             }
 
@@ -106,11 +109,13 @@
                         )
                         transaction.commit()
                     } else {
-                        Log.e(TAG, "Error, no front buffered SurfaceControl available to " +
-                            "synchronize transaction with")
+                        Log.e(
+                            TAG, "Error, no front buffered SurfaceControl available to " +
+                                "synchronize transaction with"
+                        )
                     }
                 }
-        })
+            })
         surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
 
             override fun surfaceCreated(holder: SurfaceHolder) {
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceCompat.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceCompat.kt
index 149888b..a137794 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceCompat.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceCompat.kt
@@ -18,6 +18,7 @@
 
 import android.opengl.EGL14
 import android.opengl.EGL15
+import android.opengl.GLES20
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.graphics.opengl.egl.EGLSpec
@@ -55,12 +56,26 @@
                 val eglSync: EGLSyncKHR =
                     egl.eglCreateSyncKHR(EGLExt.EGL_SYNC_NATIVE_FENCE_ANDROID, null)
                         ?: throw IllegalArgumentException("Unable to create sync object")
+                GLES20.glFlush()
                 val syncFenceCompat = SyncFenceCompat(egl.eglDupNativeFenceFDANDROID(eglSync))
                 egl.eglDestroySyncKHR(eglSync)
 
                 syncFenceCompat
             }
         }
+
+        /**
+         * An invalid signal time. Represents either the signal time for a SyncFence that isn't
+         * valid (that is, [isValid] is `false`), or if an error occurred while attempting to
+         * retrieve the signal time.
+         */
+        const val SIGNAL_TIME_INVALID: Long = -1L
+
+        /**
+         * A pending signal time. This is equivalent to the max value of a long, representing an
+         * infinitely far point in the future.
+         */
+        const val SIGNAL_TIME_PENDING: Long = Long.MAX_VALUE
     }
 
     internal constructor(syncFence: SyncFence) {
@@ -92,6 +107,22 @@
     override fun close() {
         mImpl.close()
     }
+
+    /**
+     * Returns the time that the fence signaled in the [CLOCK_MONOTONIC] time domain.
+     * This returns [SyncFence.SIGNAL_TIME_INVALID] if the SyncFence is invalid.
+     * If the fence hasn't yet signaled, then [SyncFence.SIGNAL_TIME_PENDING] is returned.
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    fun getSignalTime(): Long {
+        return mImpl.getSignalTime()
+    }
+
+    /**
+     * Checks if the SyncFence object is valid.
+     * @return `true` if it is valid, `false` otherwise
+     */
+    fun isValid() = mImpl.isValid()
 }
 
 /**
@@ -109,13 +140,15 @@
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 internal class SyncFenceCompatVerificationHelper private constructor() {
     companion object {
+        private val mEmptyAttributes = longArrayOf(EGL14.EGL_NONE.toLong())
+
         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
         @androidx.annotation.DoNotInline
         fun createSyncFenceCompatV33(): SyncFenceCompat {
             val display = EGL15.eglGetPlatformDisplay(
                 EGL15.EGL_PLATFORM_ANDROID_KHR,
                 EGL14.EGL_DEFAULT_DISPLAY.toLong(),
-                longArrayOf(EGL14.EGL_NONE.toLong()),
+                mEmptyAttributes,
                 0
             )
             if (display == EGL15.EGL_NO_DISPLAY) {
@@ -129,10 +162,10 @@
             val eglSync = EGL15.eglCreateSync(
                 display,
                 android.opengl.EGLExt.EGL_SYNC_NATIVE_FENCE_ANDROID,
-                longArrayOf(),
+                mEmptyAttributes,
                 0
             )
-
+            GLES20.glFlush()
             val syncFenceCompat = SyncFenceCompat(
                 android.opengl.EGLExt.eglDupNativeFenceFDANDROID(
                     display,
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceImpl.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceImpl.kt
index 66dc871..a0aa066 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceImpl.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceImpl.kt
@@ -16,6 +16,10 @@
 
 package androidx.graphics.lowlatency
 
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.hardware.SyncFence
+
 internal interface SyncFenceImpl {
     /**
      * Waits for a [SyncFenceImpl] to signal for up to the timeout duration
@@ -33,4 +37,18 @@
      * Close the [SyncFenceImpl]
      */
     fun close()
+
+    /**
+     * Returns the time that the fence signaled in the [CLOCK_MONOTONIC] time domain.
+     * This returns [SyncFence.SIGNAL_TIME_INVALID] if the SyncFence is invalid.
+     * If the fence hasn't yet signaled, then [SyncFence.SIGNAL_TIME_PENDING] is returned.
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    fun getSignalTime(): Long
+
+    /**
+     * Checks if the SyncFence object is valid.
+     * @return `true` if it is valid, `false` otherwise
+     */
+    fun isValid(): Boolean
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV19.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV19.kt
index 1623412..2e29eb5 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV19.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV19.kt
@@ -44,4 +44,19 @@
     override fun close() {
         mSyncFence.close()
     }
+
+    /**
+     * See [SyncFenceImpl.getSignalTime]
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    override fun getSignalTime(): Long {
+        return mSyncFence.getSignalTime()
+    }
+
+    /**
+     * See [SyncFenceImpl.isValid]
+     */
+    override fun isValid(): Boolean {
+        return mSyncFence.isValid()
+    }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV33.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV33.kt
index abcb1a0..1f039a3 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV33.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SyncFenceV33.kt
@@ -45,4 +45,19 @@
     override fun close() {
         mSyncFence.close()
     }
+
+    /**
+     * See [SyncFenceImpl.getSignalTime]
+     */
+    override fun getSignalTime(): Long {
+        return mSyncFence.signalTime
+    }
+
+    /**
+     * Checks if the SyncFence object is valid.
+     * @return `true` if it is valid, `false` otherwise
+     */
+    override fun isValid(): Boolean {
+        return mSyncFence.isValid
+    }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFence.kt b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFence.kt
index 3977a83..3963976 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFence.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFence.kt
@@ -14,22 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * Copyright 2022 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 androidx.hardware
 
 import android.os.Build
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
index 01935b5..4c4aba2 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java
@@ -964,6 +964,9 @@
         /**
          * Returns the final span index of the provided position.
          * <p>
+         * If {@link #getOrientation()} is {@link #VERTICAL}, this is a column value.
+         * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is a row value.
+         * <p>
          * If you have a faster way to calculate span index for your items, you should override
          * this method. Otherwise, you should enable span index cache
          * ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
@@ -1040,6 +1043,9 @@
         /**
          * Returns the index of the group this position belongs.
          * <p>
+         * If {@link #getOrientation()} is {@link #VERTICAL}, this is a row value.
+         * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is a column value.
+         * <p>
          * For example, if grid has 3 columns and each item occupies 1 span, span group index
          * for item 1 will be 0, item 5 will be 1.
          *
diff --git a/room/room-runtime/src/test/java/androidx/room/BuilderTest.java b/room/room-runtime/src/test/java/androidx/room/BuilderTest.java
deleted file mode 100644
index ca73919..0000000
--- a/room/room-runtime/src/test/java/androidx/room/BuilderTest.java
+++ /dev/null
@@ -1,506 +0,0 @@
-/*
- * Copyright (C) 2016 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 androidx.room;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-
-import static java.util.Arrays.asList;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.room.migration.Migration;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "deprecation"})
-@RunWith(JUnit4.class)
-public class BuilderTest {
-    @Test(expected = IllegalArgumentException.class)
-    public void nullName() {
-        //noinspection ConstantConditions
-        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, null).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyName() {
-        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, "  ").build();
-    }
-
-    @Test
-    public void executors_setQueryExecutor() {
-        Executor executor = mock(Executor.class);
-
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .setQueryExecutor(executor)
-                .build();
-
-        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
-        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
-    }
-
-    @Test
-    public void executors_setTransactionExecutor() {
-        Executor executor = mock(Executor.class);
-
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .setTransactionExecutor(executor)
-                .build();
-
-        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
-        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
-    }
-
-    @Test
-    public void executors_setBothExecutors() {
-        Executor executor1 = mock(Executor.class);
-        Executor executor2 = mock(Executor.class);
-
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .setQueryExecutor(executor1)
-                .setTransactionExecutor(executor2)
-                .build();
-
-        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor1));
-        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor2));
-    }
-
-    @Test
-    public void migration() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 1), is(asList(m1)));
-        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
-        assertThat(migrations.findMigrationPath(0, 2), is(asList(m1, m2)));
-        assertThat(migrations.findMigrationPath(2, 0), CoreMatchers.<List<Migration>>nullValue());
-        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
-    }
-
-    @Test
-    public void migrationOverride() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        Migration m3 = new EmptyMigration(0, 1);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2, m3).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 1), is(asList(m3)));
-        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
-        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
-    }
-
-    @Test
-    public void migrationJump() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        Migration m3 = new EmptyMigration(2, 3);
-        Migration m4 = new EmptyMigration(0, 3);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2, m3, m4).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 3), is(asList(m4)));
-        assertThat(migrations.findMigrationPath(1, 3), is(asList(m2, m3)));
-    }
-
-    @Test
-    public void migrationDowngrade() {
-        Migration m1_2 = new EmptyMigration(1, 2);
-        Migration m2_3 = new EmptyMigration(2, 3);
-        Migration m3_4 = new EmptyMigration(3, 4);
-        Migration m3_2 = new EmptyMigration(3, 2);
-        Migration m2_1 = new EmptyMigration(2, 1);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1_2, m2_3, m3_4, m3_2, m2_1).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(3, 2), is(asList(m3_2)));
-        assertThat(migrations.findMigrationPath(3, 1), is(asList(m3_2, m2_1)));
-    }
-
-    @Test
-    public void skipMigration() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigration()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.requireMigration, is(false));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_calledOnce_migrationsNotRequiredForValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 2).build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(2), is(false));
-
-        assertThat(config.isMigrationRequired(1, 2), is(false));
-        assertThat(config.isMigrationRequired(2, 3), is(false));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_calledTwice_migrationsNotRequiredForValues() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 2)
-                .fallbackToDestructiveMigrationFrom(3, 4)
-                .build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(2), is(false));
-        assertThat(config.isMigrationRequiredFrom(3), is(false));
-        assertThat(config.isMigrationRequiredFrom(4), is(false));
-
-        assertThat(config.isMigrationRequired(1, 2), is(false));
-        assertThat(config.isMigrationRequired(2, 3), is(false));
-        assertThat(config.isMigrationRequired(3, 4), is(false));
-        assertThat(config.isMigrationRequired(4, 5), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestructiveCalled_alwaysReturnsFalse() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigration()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(0), is(false));
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(5), is(false));
-        assertThat(config.isMigrationRequiredFrom(12), is(false));
-        assertThat(config.isMigrationRequiredFrom(132), is(false));
-
-        // Upgrades
-        assertThat(config.isMigrationRequired(0, 1), is(false));
-        assertThat(config.isMigrationRequired(1, 2), is(false));
-        assertThat(config.isMigrationRequired(5, 6), is(false));
-        assertThat(config.isMigrationRequired(7, 12), is(false));
-        assertThat(config.isMigrationRequired(132, 150), is(false));
-
-        // Downgrades
-        assertThat(config.isMigrationRequired(1, 0), is(false));
-        assertThat(config.isMigrationRequired(2, 1), is(false));
-        assertThat(config.isMigrationRequired(6, 5), is(false));
-        assertThat(config.isMigrationRequired(7, 12), is(false));
-        assertThat(config.isMigrationRequired(150, 132), is(false));
-    }
-
-    @Test
-    public void isMigrationRequired_destructiveMigrationOnDowngrade_returnTrueWhenUpgrading() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationOnDowngrade()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-
-        // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
-        assertThat(config.isMigrationRequiredFrom(0), is(true));
-        assertThat(config.isMigrationRequiredFrom(1), is(true));
-        assertThat(config.isMigrationRequiredFrom(5), is(true));
-        assertThat(config.isMigrationRequiredFrom(12), is(true));
-        assertThat(config.isMigrationRequiredFrom(132), is(true));
-
-        assertThat(config.isMigrationRequired(0, 1), is(true));
-        assertThat(config.isMigrationRequired(1, 2), is(true));
-        assertThat(config.isMigrationRequired(5, 6), is(true));
-        assertThat(config.isMigrationRequired(7, 12), is(true));
-        assertThat(config.isMigrationRequired(132, 150), is(true));
-    }
-
-    @Test
-    public void isMigrationRequired_destructiveMigrationOnDowngrade_returnFalseWhenDowngrading() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationOnDowngrade()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-
-        // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
-        assertThat(config.isMigrationRequiredFrom(0), is(true));
-        assertThat(config.isMigrationRequiredFrom(1), is(true));
-        assertThat(config.isMigrationRequiredFrom(5), is(true));
-        assertThat(config.isMigrationRequiredFrom(12), is(true));
-        assertThat(config.isMigrationRequiredFrom(132), is(true));
-
-        assertThat(config.isMigrationRequired(1, 0), is(false));
-        assertThat(config.isMigrationRequired(2, 1), is(false));
-        assertThat(config.isMigrationRequired(6 , 5), is(false));
-        assertThat(config.isMigrationRequired(12, 7), is(false));
-        assertThat(config.isMigrationRequired(150, 132), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_byDefault_alwaysReturnsTrue() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(0), is(true));
-        assertThat(config.isMigrationRequiredFrom(1), is(true));
-        assertThat(config.isMigrationRequiredFrom(5), is(true));
-        assertThat(config.isMigrationRequiredFrom(12), is(true));
-        assertThat(config.isMigrationRequiredFrom(132), is(true));
-
-        // Upgrades
-        assertThat(config.isMigrationRequired(0, 1), is(true));
-        assertThat(config.isMigrationRequired(1, 2), is(true));
-        assertThat(config.isMigrationRequired(5, 6), is(true));
-        assertThat(config.isMigrationRequired(7, 12), is(true));
-        assertThat(config.isMigrationRequired(132, 150), is(true));
-
-        // Downgrades
-        assertThat(config.isMigrationRequired(1, 0), is(true));
-        assertThat(config.isMigrationRequired(2, 1), is(true));
-        assertThat(config.isMigrationRequired(6, 5), is(true));
-        assertThat(config.isMigrationRequired(7, 12), is(true));
-        assertThat(config.isMigrationRequired(150, 132), is(true));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestFromCalled_falseForProvidedValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 4, 81)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(4), is(false));
-        assertThat(config.isMigrationRequiredFrom(81), is(false));
-
-        assertThat(config.isMigrationRequired(1, 2), is(false));
-        assertThat(config.isMigrationRequired(4, 8), is(false));
-        assertThat(config.isMigrationRequired(81, 90), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestFromCalled_trueForNonProvidedValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 4, 81)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(2), is(true));
-        assertThat(config.isMigrationRequiredFrom(3), is(true));
-        assertThat(config.isMigrationRequiredFrom(73), is(true));
-
-        assertThat(config.isMigrationRequired(2, 3), is(true));
-        assertThat(config.isMigrationRequired(3, 4), is(true));
-        assertThat(config.isMigrationRequired(73, 80), is(true));
-    }
-
-    @Test
-    public void autoMigrationShouldBeAddedToMigrations_WhenManualDowngradeMigrationIsPresent() {
-        Context context = mock(Context.class);
-
-        BuilderTest_TestDatabase_Impl db =
-                (BuilderTest_TestDatabase_Impl) Room.inMemoryDatabaseBuilder(context,
-                                TestDatabase.class)
-                        .addMigrations(new EmptyMigration(1, 0))
-                        .build();
-
-        DatabaseConfiguration config = db.mDatabaseConfiguration;
-
-        assertThat(config.migrationContainer.findMigrationPath(1, 2), is(db.mAutoMigrations));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationOnDowngrade_withProvidedValues_falseForDowngrades() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationOnDowngrade()
-                .fallbackToDestructiveMigrationFrom(2, 4).build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-
-        assertThat(config.isMigrationRequired(1, 2), is(true));
-        assertThat(config.isMigrationRequired(2, 3), is(false));
-        assertThat(config.isMigrationRequired(3, 4), is(true));
-        assertThat(config.isMigrationRequired(4, 5), is(false));
-        assertThat(config.isMigrationRequired(5, 6), is(true));
-
-        assertThat(config.isMigrationRequired(2, 1), is(false));
-        assertThat(config.isMigrationRequired(3, 2), is(false));
-        assertThat(config.isMigrationRequired(4, 3), is(false));
-        assertThat(config.isMigrationRequired(5, 4), is(false));
-        assertThat(config.isMigrationRequired(6, 5), is(false));
-    }
-
-    @Test
-    public void createBasic() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config, notNullValue());
-        assertThat(config.context, is(context));
-        assertThat(config.name, is(nullValue()));
-        assertThat(config.allowMainThreadQueries, is(false));
-        assertThat(config.journalMode, is(RoomDatabase.JournalMode.TRUNCATE));
-        assertThat(config.sqliteOpenHelperFactory,
-                instanceOf(FrameworkSQLiteOpenHelperFactory.class));
-    }
-
-    @Test
-    public void createAllowMainThread() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .allowMainThreadQueries()
-                .build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.allowMainThreadQueries, is(true));
-    }
-
-    @Test
-    public void createWriteAheadLogging() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.databaseBuilder(context, TestDatabase.class, "foo")
-                .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.journalMode, is(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING));
-    }
-
-    @Test
-    public void createWithFactoryAndVersion() {
-        Context context = mock(Context.class);
-        SupportSQLiteOpenHelper.Factory factory = mock(SupportSQLiteOpenHelper.Factory.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .openHelperFactory(factory)
-                .build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config, notNullValue());
-        assertThat(config.sqliteOpenHelperFactory, is(factory));
-    }
-
-    @Test
-    public void createFromAssetAndFromFile() {
-        Exception exception = null;
-        try {
-            Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                    .createFromAsset("assets-path")
-                    .createFromFile(new File("not-a--real-file"))
-                    .build();
-            fail("Build should have thrown");
-        } catch (Exception e) {
-            exception = e;
-        }
-        assertThat(exception, instanceOf(IllegalArgumentException.class));
-        assertThat(exception.getMessage(),
-                containsString(
-                        "More than one of createFromAsset(), createFromInputStream(), and "
-                                + "createFromFile() were called on this Builder"));
-    }
-
-    @Test
-    public void createInMemoryFromAsset() {
-        Exception exception = null;
-        try {
-            Room.inMemoryDatabaseBuilder(mock(Context.class), TestDatabase.class)
-                    .createFromAsset("assets-path")
-                    .build();
-            fail("Build should have thrown");
-        } catch (Exception e) {
-            exception = e;
-        }
-        assertThat(exception, instanceOf(IllegalArgumentException.class));
-        assertThat(exception.getMessage(),
-                containsString("Cannot create from asset or file for an in-memory"));
-    }
-
-    @Test
-    public void createInMemoryFromFile() {
-        Exception exception = null;
-        try {
-            Room.inMemoryDatabaseBuilder(mock(Context.class), TestDatabase.class)
-                    .createFromFile(new File("not-a--real-file"))
-                    .build();
-            fail("Build should have thrown");
-        } catch (Exception e) {
-            exception = e;
-        }
-        assertThat(exception, instanceOf(IllegalArgumentException.class));
-        assertThat(exception.getMessage(),
-                containsString("Cannot create from asset or file for an in-memory"));
-    }
-
-    abstract static class TestDatabase extends RoomDatabase {
-
-        DatabaseConfiguration mDatabaseConfiguration;
-
-        @Override
-        public void init(@NonNull DatabaseConfiguration configuration) {
-            super.init(configuration);
-            mDatabaseConfiguration = configuration;
-        }
-    }
-
-    static class EmptyMigration extends Migration {
-        EmptyMigration(int start, int end) {
-            super(start, end);
-        }
-
-        @Override
-        public void migrate(@NonNull SupportSQLiteDatabase database) {
-        }
-    }
-
-}
diff --git a/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt b/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt
new file mode 100644
index 0000000..cd2f9f1
--- /dev/null
+++ b/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2016 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 androidx.room
+
+import android.content.Context
+import androidx.room.Room.databaseBuilder
+import androidx.room.Room.inMemoryDatabaseBuilder
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.concurrent.Executor
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.kotlin.mock
+
+@RunWith(JUnit4::class)
+class BuilderTest {
+    @Test
+    fun nullName() {
+        try {
+            databaseBuilder(
+                mock(), RoomDatabase::class.java, null
+            ).build()
+        } catch (e: IllegalArgumentException) {
+            assertThat(e.message).isEqualTo(
+                "Cannot build a database with null or empty name. If you are trying to create an " +
+                    "in memory database, use Room.inMemoryDatabaseBuilder"
+            )
+        }
+    }
+
+    @Test
+    fun emptyName() {
+        try {
+            databaseBuilder(
+                mock(), RoomDatabase::class.java, "  "
+            ).build()
+        } catch (e: IllegalArgumentException) {
+            assertThat(e.message).isEqualTo(
+                "Cannot build a database with null or empty name. If you are trying to create an " +
+                    "in memory database, use Room.inMemoryDatabaseBuilder"
+            )
+        }
+    }
+
+    @Test
+    fun executors_setQueryExecutor() {
+        val executor: Executor = mock()
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).setQueryExecutor(executor).build()
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor).isEqualTo(executor)
+        assertThat(db.mDatabaseConfiguration.transactionExecutor).isEqualTo(executor)
+    }
+
+    @Test
+    fun executors_setTransactionExecutor() {
+        val executor: Executor = mock()
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).setTransactionExecutor(executor).build()
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor).isEqualTo(executor)
+        assertThat(db.mDatabaseConfiguration.transactionExecutor).isEqualTo(executor)
+    }
+
+    @Test
+    fun executors_setBothExecutors() {
+        val executor1: Executor = mock()
+        val executor2: Executor = mock()
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).setQueryExecutor(executor1).setTransactionExecutor(executor2).build()
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor).isEqualTo(executor1)
+        assertThat(db.mDatabaseConfiguration.transactionExecutor).isEqualTo(executor2)
+    }
+
+    @Test
+    fun migration() {
+        val m1: Migration = EmptyMigration(0, 1)
+        val m2: Migration = EmptyMigration(1, 2)
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).addMigrations(m1, m2).build()
+
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        val migrations = config.migrationContainer
+
+        assertThat(migrations.findMigrationPath(0, 1)).containsExactlyElementsIn(listOf(m1))
+        assertThat(migrations.findMigrationPath(1, 2)).containsExactlyElementsIn(listOf(m2))
+        assertThat(migrations.findMigrationPath(0, 2))
+            .containsExactlyElementsIn(listOf(m1, m2))
+        assertThat(migrations.findMigrationPath(2, 0)).isNull()
+        assertThat(migrations.findMigrationPath(0, 3)).isNull()
+    }
+
+    @Test
+    fun migrationOverride() {
+        val m1: Migration = EmptyMigration(0, 1)
+        val m2: Migration = EmptyMigration(1, 2)
+        val m3: Migration = EmptyMigration(0, 1)
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).addMigrations(m1, m2, m3).build()
+
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        val migrations = config.migrationContainer
+
+        assertThat(migrations.findMigrationPath(0, 1)).containsExactlyElementsIn(listOf(m3))
+        assertThat(migrations.findMigrationPath(1, 2)).containsExactlyElementsIn(listOf(m2))
+        assertThat(migrations.findMigrationPath(0, 3)).isNull()
+    }
+
+    @Test
+    fun migrationJump() {
+        val m1: Migration = EmptyMigration(0, 1)
+        val m2: Migration = EmptyMigration(1, 2)
+        val m3: Migration = EmptyMigration(2, 3)
+        val m4: Migration = EmptyMigration(0, 3)
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        ).addMigrations(m1, m2, m3, m4).build()
+
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        val migrations = config.migrationContainer
+
+        assertThat(migrations.findMigrationPath(0, 3)).containsExactlyElementsIn(listOf(m4))
+        assertThat(migrations.findMigrationPath(1, 3))
+            .containsExactlyElementsIn(listOf(m2, m3))
+    }
+
+    @Test
+    fun migrationDowngrade() {
+        val m1_2: Migration = EmptyMigration(1, 2)
+        val m2_3: Migration = EmptyMigration(2, 3)
+        val m3_4: Migration = EmptyMigration(3, 4)
+        val m3_2: Migration = EmptyMigration(3, 2)
+        val m2_1: Migration = EmptyMigration(2, 1)
+        val db = databaseBuilder(
+            mock(), TestDatabase::class.java, "foo"
+        )
+            .addMigrations(m1_2, m2_3, m3_4, m3_2, m2_1).build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        val migrations = config.migrationContainer
+        assertThat(migrations.findMigrationPath(3, 2))
+            .containsExactlyElementsIn(listOf(m3_2))
+        assertThat(migrations.findMigrationPath(3, 1))
+            .containsExactlyElementsIn(listOf(m3_2, m2_1))
+    }
+
+    @Test
+    fun skipMigration() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigration()
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.requireMigration).isFalse()
+    }
+
+    @Test
+    fun fallbackToDestructiveMigrationFrom_calledOnce_migrationsNotRequiredForValues() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationFrom(1, 2).build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.isMigrationRequired(1, 2)).isFalse()
+        assertThat(config.isMigrationRequired(2, 3)).isFalse()
+    }
+
+    @Test
+    fun fallbackToDestructiveMigrationFrom_calledTwice_migrationsNotRequiredForValues() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationFrom(1, 2)
+            .fallbackToDestructiveMigrationFrom(3, 4)
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.isMigrationRequired(1, 2)).isFalse()
+        assertThat(config.isMigrationRequired(2, 3)).isFalse()
+        assertThat(config.isMigrationRequired(3, 4)).isFalse()
+        assertThat(config.isMigrationRequired(4, 5)).isFalse()
+    }
+
+    @Test
+    fun isMigrationRequiredFrom_fallBackToDestructiveCalled_alwaysReturnsFalse() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigration()
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+
+        assertThat(config.isMigrationRequired(0, 1)).isFalse()
+        assertThat(config.isMigrationRequired(1, 2)).isFalse()
+        assertThat(config.isMigrationRequired(5, 6)).isFalse()
+        assertThat(config.isMigrationRequired(7, 12)).isFalse()
+        assertThat(config.isMigrationRequired(132, 150)).isFalse()
+
+        assertThat(config.isMigrationRequired(1, 0)).isFalse()
+        assertThat(config.isMigrationRequired(2, 1)).isFalse()
+        assertThat(config.isMigrationRequired(6, 5)).isFalse()
+        assertThat(config.isMigrationRequired(7, 12)).isFalse()
+        assertThat(config.isMigrationRequired(150, 132)).isFalse()
+    }
+
+    // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
+    @Test
+    fun isMigrationRequired_destructiveMigrationOnDowngrade_returnTrueWhenUpgrading() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationOnDowngrade()
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+
+        // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
+        assertThat(config.isMigrationRequired(0, 1)).isTrue()
+        assertThat(config.isMigrationRequired(1, 2)).isTrue()
+        assertThat(config.isMigrationRequired(5, 6)).isTrue()
+        assertThat(config.isMigrationRequired(7, 12)).isTrue()
+        assertThat(config.isMigrationRequired(132, 150)).isTrue()
+    }
+
+    // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
+    @Test
+    fun isMigrationRequired_destructiveMigrationOnDowngrade_returnFalseWhenDowngrading() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationOnDowngrade()
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+
+        // isMigrationRequiredFrom doesn't know about downgrade only so it always returns true
+        assertThat(config.isMigrationRequired(1, 0)).isFalse()
+        assertThat(config.isMigrationRequired(2, 1)).isFalse()
+        assertThat(config.isMigrationRequired(6, 5)).isFalse()
+        assertThat(config.isMigrationRequired(12, 7)).isFalse()
+        assertThat(config.isMigrationRequired(150, 132)).isFalse()
+    }
+
+    @Test
+    fun isMigrationRequiredFrom_byDefault_alwaysReturnsTrue() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+
+        assertThat(config.isMigrationRequired(0, 1)).isTrue()
+        assertThat(config.isMigrationRequired(1, 2)).isTrue()
+        assertThat(config.isMigrationRequired(5, 6)).isTrue()
+        assertThat(config.isMigrationRequired(7, 12)).isTrue()
+        assertThat(config.isMigrationRequired(132, 150)).isTrue()
+
+        assertThat(config.isMigrationRequired(1, 0)).isTrue()
+        assertThat(config.isMigrationRequired(2, 1)).isTrue()
+        assertThat(config.isMigrationRequired(6, 5)).isTrue()
+        assertThat(config.isMigrationRequired(7, 12)).isTrue()
+        assertThat(config.isMigrationRequired(150, 132)).isTrue()
+    }
+
+    @Test
+    fun isMigrationRequiredFrom_fallBackToDestFromCalled_falseForProvidedValues() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationFrom(1, 4, 81)
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.isMigrationRequired(1, 2)).isFalse()
+        assertThat(config.isMigrationRequired(4, 8)).isFalse()
+        assertThat(config.isMigrationRequired(81, 90)).isFalse()
+    }
+
+    @Test
+    fun isMigrationRequiredFrom_fallBackToDestFromCalled_trueForNonProvidedValues() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationFrom(1, 4, 81)
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.isMigrationRequired(2, 3)).isTrue()
+        assertThat(config.isMigrationRequired(3, 4)).isTrue()
+        assertThat(config.isMigrationRequired(73, 80)).isTrue()
+    }
+
+    @Test
+    fun autoMigrationShouldBeAddedToMigrations_WhenManualDowngradeMigrationIsPresent() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(
+            context,
+            TestDatabase::class.java
+        )
+            .addMigrations(EmptyMigration(1, 0))
+            .build() as BuilderTest_TestDatabase_Impl
+        val config: DatabaseConfiguration = db.mDatabaseConfiguration
+        assertThat(
+            config.migrationContainer.findMigrationPath(1, 2)).isEqualTo((db.mAutoMigrations)
+        )
+    }
+
+    @Test
+    fun fallbackToDestructiveMigrationOnDowngrade_withProvidedValues_falseForDowngrades() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .fallbackToDestructiveMigrationOnDowngrade()
+            .fallbackToDestructiveMigrationFrom(2, 4).build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.isMigrationRequired(1, 2)).isTrue()
+        assertThat(config.isMigrationRequired(2, 3)).isFalse()
+        assertThat(config.isMigrationRequired(3, 4)).isTrue()
+        assertThat(config.isMigrationRequired(4, 5)).isFalse()
+        assertThat(config.isMigrationRequired(5, 6)).isTrue()
+        assertThat(config.isMigrationRequired(2, 1)).isFalse()
+        assertThat(config.isMigrationRequired(3, 2)).isFalse()
+        assertThat(config.isMigrationRequired(4, 3)).isFalse()
+        assertThat(config.isMigrationRequired(5, 4)).isFalse()
+        assertThat(config.isMigrationRequired(6, 5)).isFalse()
+    }
+
+    @Test
+    fun createBasic() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java).build()
+        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config).isNotNull()
+        assertThat(config.context).isEqualTo(context)
+        assertThat(config.name).isNull()
+        assertThat(config.allowMainThreadQueries).isFalse()
+        assertThat(config.journalMode).isEqualTo(RoomDatabase.JournalMode.TRUNCATE)
+        assertThat(config.sqliteOpenHelperFactory)
+            .isInstanceOf(FrameworkSQLiteOpenHelperFactory::class.java)
+    }
+
+    @Test
+    fun createAllowMainThread() {
+        val context: Context = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .allowMainThreadQueries()
+            .build()
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.allowMainThreadQueries).isTrue()
+    }
+
+    @Test
+    fun createWriteAheadLogging() {
+        val context: Context = mock()
+        val db = databaseBuilder(context, TestDatabase::class.java, "foo")
+            .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build()
+        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config.journalMode).isEqualTo(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
+    }
+
+    @Test
+    fun createWithFactoryAndVersion() {
+        val context: Context = mock()
+        val factory: SupportSQLiteOpenHelper.Factory = mock()
+        val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
+            .openHelperFactory(factory)
+            .build()
+        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
+        assertThat(config).isNotNull()
+        assertThat(config.sqliteOpenHelperFactory).isEqualTo(factory)
+    }
+
+    @Test
+    fun createFromAssetAndFromFile() {
+        var exception: Exception? = null
+        try {
+            databaseBuilder(
+                mock(),
+                TestDatabase::class.java,
+                "foo"
+            )
+                .createFromAsset("assets-path")
+                .createFromFile(File("not-a--real-file"))
+                .build()
+            Assert.fail("Build should have thrown")
+        } catch (e: Exception) {
+            exception = e
+        }
+        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).hasMessageThat().contains("More than one of createFromAsset(), " +
+            "createFromInputStream(), and createFromFile() were called on this Builder")
+    }
+
+    @Test
+    fun createInMemoryFromAsset() {
+        var exception: Exception? = null
+        try {
+            inMemoryDatabaseBuilder(
+                mock(),
+                TestDatabase::class.java
+            )
+                .createFromAsset("assets-path")
+                .build()
+            Assert.fail("Build should have thrown")
+        } catch (e: Exception) {
+            exception = e
+        }
+        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).hasMessageThat().contains(
+            "Cannot create from asset or file for an in-memory"
+        )
+    }
+
+    @Test
+    fun createInMemoryFromFile() {
+        var exception: Exception? = null
+        try {
+            inMemoryDatabaseBuilder(
+                mock(),
+                TestDatabase::class.java
+            )
+                .createFromFile(File("not-a--real-file"))
+                .build()
+            Assert.fail("Build should have thrown")
+        } catch (e: Exception) {
+            exception = e
+        }
+        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).hasMessageThat().contains(
+            "Cannot create from asset or file for an in-memory"
+        )
+    }
+
+    internal abstract class TestDatabase : RoomDatabase() {
+        lateinit var mDatabaseConfiguration: DatabaseConfiguration
+        override fun init(configuration: DatabaseConfiguration) {
+            super.init(configuration)
+            mDatabaseConfiguration = configuration
+        }
+    }
+
+    internal class EmptyMigration(start: Int, end: Int) : Migration(start, end) {
+        override fun migrate(database: SupportSQLiteDatabase) {}
+    }
+}
diff --git a/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.java b/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.java
deleted file mode 100644
index f475782..0000000
--- a/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.room;
-
-import androidx.annotation.NonNull;
-import androidx.room.migration.AutoMigrationSpec;
-import androidx.room.migration.Migration;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-
-import org.mockito.Mockito;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-public class BuilderTest_TestDatabase_Impl extends BuilderTest.TestDatabase {
-    DatabaseConfiguration mConfig;
-    List<Migration> mAutoMigrations = Arrays.asList(new BuilderTest.EmptyMigration(1, 2));
-
-    @Override
-    public void init(DatabaseConfiguration configuration) {
-        super.init(configuration);
-        mConfig = configuration;
-    }
-
-    @Override
-    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
-        return Mockito.mock(SupportSQLiteOpenHelper.class);
-    }
-
-    @Override
-    protected InvalidationTracker createInvalidationTracker() {
-        return Mockito.mock(InvalidationTracker.class);
-    }
-
-    @Override
-    public void clearAllTables() {
-    }
-
-    @NonNull
-    @Override
-    public List<Migration> getAutoMigrations(
-            @NonNull Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> autoMigrationSpecs
-    ) {
-        return mAutoMigrations;
-    }
-}
\ No newline at end of file
diff --git a/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.kt b/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.kt
new file mode 100644
index 0000000..482b0fa
--- /dev/null
+++ b/room/room-runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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 androidx.room
+
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import org.mockito.kotlin.mock
+
+internal class BuilderTest_TestDatabase_Impl : BuilderTest.TestDatabase() {
+    lateinit var mConfig: DatabaseConfiguration
+    var mAutoMigrations = listOf<Migration>(BuilderTest.EmptyMigration(1, 2))
+    override fun init(configuration: DatabaseConfiguration) {
+        super.init(configuration)
+        mConfig = configuration
+    }
+
+    override fun createOpenHelper(config: DatabaseConfiguration): SupportSQLiteOpenHelper {
+        return mock()
+    }
+
+    override fun createInvalidationTracker(): InvalidationTracker {
+        return mock()
+    }
+
+    override fun clearAllTables() {}
+    override fun getAutoMigrations(
+        autoMigrationSpecs: Map<Class<out AutoMigrationSpec>, AutoMigrationSpec>
+    ): List<Migration> {
+        return mAutoMigrations
+    }
+}
\ No newline at end of file
diff --git a/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java b/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java
deleted file mode 100644
index 2106fdb..0000000
--- a/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.room;
-
-
-import static androidx.room.InvalidationTracker.ObservedTableTracker.ADD;
-import static androidx.room.InvalidationTracker.ObservedTableTracker.NO_OP;
-import static androidx.room.InvalidationTracker.ObservedTableTracker.REMOVE;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@RunWith(JUnit4.class)
-public class ObservedTableTrackerTest {
-    private static final int TABLE_COUNT = 5;
-    private InvalidationTracker.ObservedTableTracker mTracker;
-
-    @Before
-    public void setup() {
-        mTracker = new InvalidationTracker.ObservedTableTracker(TABLE_COUNT);
-    }
-
-    @Test
-    public void basicAdd() {
-        mTracker.onAdded(2, 3);
-        assertThat(mTracker.getTablesToSync(), is(createResponse(2, ADD, 3, ADD)));
-    }
-
-    @Test
-    public void basicRemove() {
-        initState(2, 3);
-        mTracker.onRemoved(3);
-        assertThat(mTracker.getTablesToSync(), is(createResponse(3, REMOVE)));
-    }
-
-    @Test
-    public void noChange() {
-        initState(1, 3);
-        mTracker.onAdded(3);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-    }
-
-    @Test
-    public void multipleAdditionsDeletions() {
-        initState(2, 4);
-        mTracker.onAdded(2);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onAdded(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onRemoved(2);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onRemoved(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onAdded(1, 3);
-        mTracker.onRemoved(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(
-                createResponse(1, ADD, 2, REMOVE, 3, ADD, 4, REMOVE)));
-    }
-
-    private void initState(int... tableIds) {
-        mTracker.onAdded(tableIds);
-        mTracker.getTablesToSync();
-    }
-
-    private static int[] createResponse(int... tuples) {
-        int[] result = new int[TABLE_COUNT];
-        Arrays.fill(result, NO_OP);
-        for (int i = 0; i < tuples.length; i += 2) {
-            result[tuples[i]] = tuples[i + 1];
-        }
-        return result;
-    }
-}
diff --git a/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt b/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt
new file mode 100644
index 0000000..10ac217
--- /dev/null
+++ b/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 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 androidx.room
+
+import com.google.common.truth.Truth.assertThat
+import java.util.Arrays
+import kotlin.test.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ObservedTableTrackerTest {
+    private lateinit var mTracker: InvalidationTracker.ObservedTableTracker
+    @Before
+    fun setup() {
+        mTracker = InvalidationTracker.ObservedTableTracker(TABLE_COUNT)
+    }
+
+    @Test
+    fun basicAdd() {
+        mTracker.onAdded(2, 3)
+        assertThat(
+            mTracker.getTablesToSync()
+        ).isEqualTo(
+            createResponse(
+                2,
+                InvalidationTracker.ObservedTableTracker.ADD,
+                3,
+                InvalidationTracker.ObservedTableTracker.ADD
+            )
+        )
+    }
+
+    @Test
+    fun basicRemove() {
+        initState(2, 3)
+        mTracker.onRemoved(3)
+        assertThat(
+            mTracker.getTablesToSync()
+        ).isEqualTo(
+            createResponse(3, InvalidationTracker.ObservedTableTracker.REMOVE)
+        )
+    }
+
+    @Test
+    fun noChange() {
+        initState(1, 3)
+        mTracker.onAdded(3)
+        assertNull(
+            mTracker.getTablesToSync()
+        )
+    }
+
+    @Test
+    fun multipleAdditionsDeletions() {
+        initState(2, 4)
+        mTracker.onAdded(2)
+        assertNull(
+            mTracker.getTablesToSync()
+        )
+        mTracker.onAdded(2, 4)
+        assertNull(
+            mTracker.getTablesToSync()
+        )
+        mTracker.onRemoved(2)
+        assertNull(
+            mTracker.getTablesToSync()
+        )
+        mTracker.onRemoved(2, 4)
+        assertNull(
+            mTracker.getTablesToSync()
+        )
+        mTracker.onAdded(1, 3)
+        mTracker.onRemoved(2, 4)
+        assertThat(
+            mTracker.getTablesToSync()
+        ).isEqualTo(
+            createResponse(
+                1,
+                InvalidationTracker.ObservedTableTracker.ADD,
+                2,
+                InvalidationTracker.ObservedTableTracker.REMOVE,
+                3,
+                InvalidationTracker.ObservedTableTracker.ADD,
+                4,
+                InvalidationTracker.ObservedTableTracker.REMOVE
+            )
+        )
+    }
+
+    private fun initState(vararg tableIds: Int) {
+        mTracker.onAdded(*tableIds)
+        mTracker.getTablesToSync()
+    }
+
+    companion object {
+        private const val TABLE_COUNT = 5
+        private fun createResponse(vararg tuples: Int): IntArray {
+            val result = IntArray(TABLE_COUNT)
+            Arrays.fill(result, InvalidationTracker.ObservedTableTracker.NO_OP)
+            var i = 0
+            while (i < tuples.size) {
+                result[tuples[i]] = tuples[i + 1]
+                i += 2
+            }
+            return result
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java b/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java
deleted file mode 100644
index 0738a49..0000000
--- a/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 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 androidx.room;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import androidx.sqlite.db.SupportSQLiteProgram;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RoomSQLiteQueryTest {
-    @Before
-    public void clear() {
-        RoomSQLiteQuery.queryPool.clear();
-    }
-
-    @Test
-    public void acquireBasic() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        assertThat(query.getSql(), is("abc"));
-        assertThat(query.getArgCount(), is(3));
-        assertThat(query.blobBindings.length, is(4));
-        assertThat(query.longBindings.length, is(4));
-        assertThat(query.stringBindings.length, is(4));
-        assertThat(query.doubleBindings.length, is(4));
-    }
-
-    @Test
-    public void acquireSameSizeAgain() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("blah", 3), sameInstance(query));
-    }
-
-    @Test
-    public void acquireSameSizeWithoutRelease() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        assertThat(RoomSQLiteQuery.acquire("fda", 3), not(sameInstance(query)));
-    }
-
-    @Test
-    public void bindings() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 6);
-        byte[] myBlob = new byte[3];
-        long myLong = 3L;
-        double myDouble = 7.0;
-        String myString = "ss";
-        query.bindBlob(1, myBlob);
-        query.bindLong(2, myLong);
-        query.bindNull(3);
-        query.bindDouble(4, myDouble);
-        query.bindString(5, myString);
-        query.bindNull(6);
-        SupportSQLiteProgram program = mock(SupportSQLiteProgram.class);
-        query.bindTo(program);
-
-        verify(program).bindBlob(1, myBlob);
-        verify(program).bindLong(2, myLong);
-        verify(program).bindNull(3);
-        verify(program).bindDouble(4, myDouble);
-        verify(program).bindString(5, myString);
-        verify(program).bindNull(6);
-    }
-
-    @Test
-    public void dontKeepSameSizeTwice() {
-        RoomSQLiteQuery query1 = RoomSQLiteQuery.acquire("abc", 3);
-        RoomSQLiteQuery query2 = RoomSQLiteQuery.acquire("zx", 3);
-        RoomSQLiteQuery query3 = RoomSQLiteQuery.acquire("qw", 0);
-
-        query1.release();
-        query2.release();
-        assertThat(RoomSQLiteQuery.queryPool.size(), is(1));
-
-        query3.release();
-        assertThat(RoomSQLiteQuery.queryPool.size(), is(2));
-    }
-
-    @Test
-    public void returnExistingForSmallerSize() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("dsa", 2), sameInstance(query));
-    }
-
-    @Test
-    public void returnNewForBigger() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("dsa", 4), not(sameInstance(query)));
-    }
-
-    @Test
-    public void pruneCache() {
-        for (int i = 0; i < RoomSQLiteQuery.POOL_LIMIT; i++) {
-            RoomSQLiteQuery.acquire("dsdsa", i).release();
-        }
-        pruneCacheTest();
-    }
-
-    @Test
-    public void pruneCacheReverseInsertion() {
-        List<RoomSQLiteQuery> queries = new ArrayList<>();
-        for (int i = RoomSQLiteQuery.POOL_LIMIT - 1; i >= 0; i--) {
-            queries.add(RoomSQLiteQuery.acquire("dsdsa", i));
-        }
-        for (RoomSQLiteQuery query : queries) {
-            query.release();
-        }
-        pruneCacheTest();
-    }
-
-    private void pruneCacheTest() {
-        assertThat(RoomSQLiteQuery.queryPool.size(), is(RoomSQLiteQuery.POOL_LIMIT));
-        RoomSQLiteQuery.acquire("dsadsa", RoomSQLiteQuery.POOL_LIMIT + 1).release();
-        assertThat(RoomSQLiteQuery.queryPool.size(), is(RoomSQLiteQuery.DESIRED_POOL_SIZE));
-        Iterator<RoomSQLiteQuery> itr = RoomSQLiteQuery.queryPool.values().iterator();
-        for (int i = 0; i < RoomSQLiteQuery.DESIRED_POOL_SIZE; i++) {
-            assertThat(itr.next().getCapacity(), is(i));
-        }
-    }
-}
diff --git a/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt b/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt
new file mode 100644
index 0000000..669b7f2
--- /dev/null
+++ b/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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 androidx.room
+
+import androidx.room.RoomSQLiteQuery.Companion.acquire
+import androidx.sqlite.db.SupportSQLiteProgram
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@RunWith(JUnit4::class)
+class RoomSQLiteQueryTest {
+    @Before
+    fun clear() {
+        RoomSQLiteQuery.queryPool.clear()
+    }
+
+    @Test
+    fun acquireBasic() {
+        val query = acquire("abc", 3)
+        assertThat(query.sql).isEqualTo("abc")
+        assertThat(query.argCount).isEqualTo(3)
+        assertThat(query.blobBindings.size).isEqualTo(4)
+        assertThat(query.longBindings.size).isEqualTo(4)
+        assertThat(query.stringBindings.size).isEqualTo(4)
+        assertThat(query.doubleBindings.size).isEqualTo(4)
+    }
+
+    @Test
+    fun acquireSameSizeAgain() {
+        val query = acquire("abc", 3)
+        query.release()
+        assertThat(acquire("blah", 3)).isSameInstanceAs(query)
+    }
+
+    @Test
+    fun acquireSameSizeWithoutRelease() {
+        val query = acquire("abc", 3)
+        assertThat(
+            acquire("fda", 3)
+        ).isNotSameInstanceAs(query)
+    }
+
+    @Test
+    fun bindings() {
+        val query = acquire("abc", 6)
+        val myBlob = ByteArray(3)
+        val myLong = 3L
+        val myDouble = 7.0
+        val myString = "ss"
+        query.bindBlob(1, myBlob)
+        query.bindLong(2, myLong)
+        query.bindNull(3)
+        query.bindDouble(4, myDouble)
+        query.bindString(5, myString)
+        query.bindNull(6)
+        val program: SupportSQLiteProgram = mock()
+        query.bindTo(program)
+        verify(program).bindBlob(1, myBlob)
+        verify(program).bindLong(2, myLong)
+        verify(program).bindNull(3)
+        verify(program).bindDouble(4, myDouble)
+        verify(program).bindString(5, myString)
+        verify(program).bindNull(6)
+    }
+
+    @Test
+    fun dontKeepSameSizeTwice() {
+        val query1 = acquire("abc", 3)
+        val query2 = acquire("zx", 3)
+        val query3 = acquire("qw", 0)
+        query1.release()
+        query2.release()
+        assertThat(RoomSQLiteQuery.queryPool.size).isEqualTo(1)
+        query3.release()
+        assertThat(RoomSQLiteQuery.queryPool.size).isEqualTo(2)
+    }
+
+    @Test
+    fun returnExistingForSmallerSize() {
+        val query = acquire("abc", 3)
+        query.release()
+        assertThat(acquire("dsa", 2)).isSameInstanceAs(query)
+    }
+
+    @Test
+    fun returnNewForBigger() {
+        val query = acquire("abc", 3)
+        query.release()
+        assertThat(
+            acquire("dsa", 4)
+        ).isNotSameInstanceAs(query)
+    }
+
+    @Test
+    fun pruneCache() {
+        for (i in 0 until RoomSQLiteQuery.POOL_LIMIT) {
+            acquire("dsdsa", i).release()
+        }
+        pruneCacheTest()
+    }
+
+    @Test
+    fun pruneCacheReverseInsertion() {
+        val queries: MutableList<RoomSQLiteQuery> = ArrayList()
+        for (i in RoomSQLiteQuery.POOL_LIMIT - 1 downTo 0) {
+            queries.add(acquire("dsdsa", i))
+        }
+        for (query in queries) {
+            query.release()
+        }
+        pruneCacheTest()
+    }
+
+    private fun pruneCacheTest() {
+        assertThat(
+            RoomSQLiteQuery.queryPool.size
+        ).isEqualTo(RoomSQLiteQuery.POOL_LIMIT)
+        acquire("dsadsa", RoomSQLiteQuery.POOL_LIMIT + 1).release()
+        assertThat(
+            RoomSQLiteQuery.queryPool.size
+        ).isEqualTo(RoomSQLiteQuery.DESIRED_POOL_SIZE)
+        val itr: Iterator<RoomSQLiteQuery> = RoomSQLiteQuery.queryPool.values.iterator()
+        for (i in 0 until RoomSQLiteQuery.DESIRED_POOL_SIZE) {
+            assertThat(itr.next().capacity).isEqualTo(i)
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java b/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java
deleted file mode 100644
index dde3a3a..0000000
--- a/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 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 androidx.room;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.sqlite.db.SupportSQLiteStatement;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-@RunWith(JUnit4.class)
-public class SharedSQLiteStatementTest {
-    private SharedSQLiteStatement mSharedStmt;
-    RoomDatabase mDb;
-    @Before
-    public void init() {
-        mDb = mock(RoomDatabase.class);
-        when(mDb.compileStatement(anyString())).thenAnswer(new Answer<SupportSQLiteStatement>() {
-
-            @Override
-            public SupportSQLiteStatement answer(InvocationOnMock invocation) throws Throwable {
-                return mock(SupportSQLiteStatement.class);
-            }
-        });
-        when(mDb.getInvalidationTracker()).thenReturn(mock(InvalidationTracker.class));
-        mSharedStmt = new SharedSQLiteStatement(mDb) {
-            @Override
-            protected String createQuery() {
-                return "foo";
-            }
-        };
-    }
-
-    @Test
-    public void checkMainThread() {
-        mSharedStmt.acquire();
-        verify(mDb).assertNotMainThread();
-    }
-
-    @Test
-    public void basic() {
-        assertThat(mSharedStmt.acquire(), notNullValue());
-    }
-
-    @Test
-    public void getTwiceWithoutReleasing() {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt2, notNullValue());
-        assertThat(stmt1, is(not(stmt2)));
-    }
-
-    @Test
-    public void getTwiceWithReleasing() {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        mSharedStmt.release(stmt1);
-        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt1, is(stmt2));
-    }
-
-    @Test
-    public void getFromAnotherThreadWhileHolding() throws ExecutionException, InterruptedException {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
-                new Callable<SupportSQLiteStatement>() {
-                    @Override
-                    public SupportSQLiteStatement call() throws Exception {
-                        return mSharedStmt.acquire();
-                    }
-                });
-        new Thread(task).run();
-        SupportSQLiteStatement stmt2 = task.get();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt2, notNullValue());
-        assertThat(stmt1, is(not(stmt2)));
-    }
-
-    @Test
-    public void getFromAnotherThreadAfterReleasing() throws ExecutionException,
-            InterruptedException {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        mSharedStmt.release(stmt1);
-        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
-                new Callable<SupportSQLiteStatement>() {
-                    @Override
-                    public SupportSQLiteStatement call() throws Exception {
-                        return mSharedStmt.acquire();
-                    }
-                });
-        new Thread(task).run();
-        SupportSQLiteStatement stmt2 = task.get();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt1, is(stmt2));
-    }
-}
diff --git a/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt b/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt
new file mode 100644
index 0000000..c9da445
--- /dev/null
+++ b/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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 androidx.room
+
+import androidx.sqlite.db.SupportSQLiteStatement
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.FutureTask
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(JUnit4::class)
+class SharedSQLiteStatementTest {
+    private lateinit var mSharedStmt: SharedSQLiteStatement
+    lateinit var mDb: RoomDatabase
+    @Before
+    fun init() {
+        val mdata: RoomDatabase = mock()
+        whenever(mdata.compileStatement(anyOrNull())).thenAnswer {
+            mock<SupportSQLiteStatement>()
+        }
+        whenever(mdata.invalidationTracker).thenReturn(mock())
+        mDb = mdata
+        mSharedStmt = object : SharedSQLiteStatement(mdata) {
+            override fun createQuery(): String {
+                return "foo"
+            }
+        }
+    }
+
+    @Test
+    fun checkMainThread() {
+        mSharedStmt.acquire()
+        verify(mDb).assertNotMainThread()
+    }
+
+    @Test
+    fun basic() {
+        assertThat(mSharedStmt.acquire()).isNotNull()
+    }
+
+    @Test
+    fun twiceWithoutReleasing() {
+            val stmt1 = mSharedStmt.acquire()
+            val stmt2 = mSharedStmt.acquire()
+            assertThat(stmt1).isNotNull()
+            assertThat(stmt2).isNotNull()
+            assertThat(stmt1).isNotEqualTo(stmt2)
+        }
+
+    @Test
+    fun twiceWithReleasing() {
+            val stmt1 = mSharedStmt.acquire()
+            mSharedStmt.release(stmt1)
+            val stmt2 = mSharedStmt.acquire()
+            assertThat(stmt1).isNotNull()
+            assertThat(stmt1).isEqualTo(stmt2)
+        }
+
+    @Test
+    fun fromAnotherThreadWhileHolding() {
+        val stmt1 = mSharedStmt.acquire()
+        val task = FutureTask { mSharedStmt.acquire() }
+        Thread(task).start()
+        val stmt2 = task.get()
+        assertThat(stmt1).isNotNull()
+        assertThat(stmt2).isNotNull()
+        assertThat(stmt1).isNotEqualTo(stmt2)
+    }
+
+    @Test
+    fun fromAnotherThreadAfterReleasing() {
+        val stmt1 = mSharedStmt.acquire()
+        mSharedStmt.release(stmt1)
+        val task = FutureTask { mSharedStmt.acquire() }
+        Thread(task).start()
+        val stmt2 = task.get()
+        assertThat(stmt1).isNotNull()
+        assertThat(stmt1).isEqualTo(stmt2)
+    }
+}
\ No newline at end of file