Handle special case SQLite exception during upsert.

When 2067 SQLITE_CONSTRAINT_UNIQUE is thrown during an upsert, upsert should perform an update.

Bug: 243039555
Test: EntityUpsertionAdapterTest.java
Change-Id: If28499c59443f590ac456924eff03b18f1a87e4f
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EntityUpsertionAdapterTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EntityUpsertionAdapterTest.java
index 071d890..8e7343a 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EntityUpsertionAdapterTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EntityUpsertionAdapterTest.java
@@ -45,6 +45,7 @@
 
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -235,6 +236,33 @@
     }
 
     @Test
+    public void upsertFKUnique2067Error() {
+        Pet pet = new Pet();
+        pet.setPetId(232);
+        pet.setName(UUID.randomUUID().toString());
+        pet.setAdoptionDate(new Date());
+        mInsertionAdapter.insert(pet);
+
+        Toy testToy = new Toy();
+        testToy.setId(2);
+        testToy.setName("toy name");
+        testToy.setPetId(232);
+
+        Toy testToy2 = new Toy();
+        testToy2.setId(3);
+        testToy2.setName("toy name");
+        testToy2.setPetId(232);
+
+        mUpsertionAdapter.upsertAndReturnId(pet);
+        mUpsertionAdapterToy.upsertAndReturnId(testToy);
+        try {
+            mUpsertionAdapterToy.upsertAndReturnId(testToy2);
+        } catch (SQLiteConstraintException ex) {
+            assertThat(ex.toString().contains("2067"));
+        }
+    }
+
+    @Test
     public void testUpsertWithoutTryCatch() {
         Pet testPet = TestUtil.createPet(232);
         Pet testPet2 = TestUtil.createPet(232);
diff --git a/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt b/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
index b1c65a8..db31e11 100644
--- a/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
+++ b/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
@@ -17,14 +17,18 @@
 package androidx.room
 
 import android.database.sqlite.SQLiteConstraintException
-import android.os.Build
 import androidx.annotation.RestrictTo
 
 /**
- * The ErrorCode defined by SQLite Library for SQLITE_CONSTRAINT_PRIMARYKEY error
- * Only used by android of version newer than 19
+ * The error code defined by SQLite Library for SQLITE_CONSTRAINT_PRIMARYKEY error
+ * Only used by android of version newer than 19.
  */
-private const val ErrorCode = "1555"
+private const val SQLITE_CONSTRAINT_PRIMARYKEY = "1555"
+
+/**
+ * The error code defined by SQLite Library for SQLITE_CONSTRAINT_UNIQUE error.
+ */
+private const val SQLITE_CONSTRAINT_UNIQUE = "2067"
 
 /**
  * For android of version below and including 19, use error message instead of
@@ -207,18 +211,13 @@
      */
     private fun checkUniquenessException(ex: SQLiteConstraintException) {
         val message = ex.message ?: throw ex
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
-            if (!message.contains(ErrorMsg, ignoreCase = true) && !message.contains(
-                    ErrorCode,
-                    ignoreCase = true
-                )
-            ) {
-                throw ex
-            }
-        } else {
-            if (!message.contains(ErrorCode, ignoreCase = true)) {
-                throw ex
-            }
+        val hasUniqueConstraintEx =
+            message.contains(ErrorMsg, ignoreCase = true) ||
+                message.contains(SQLITE_CONSTRAINT_UNIQUE) ||
+                message.contains(SQLITE_CONSTRAINT_PRIMARYKEY)
+
+        if (!hasUniqueConstraintEx) {
+            throw ex
         }
     }
 }