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