Merge "Add AutoClosingRoomOpenHelper to support AutoClosing room databases." into androidx-main
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
new file mode 100644
index 0000000..febc98c
--- /dev/null
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import android.content.Context
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+
+public class AutoClosingRoomOpenHelperFactoryTest {
+    private val DB_NAME = "name"
+
+    @Before
+    public fun setUp() {
+        ApplicationProvider.getApplicationContext<Context>().deleteDatabase(DB_NAME)
+    }
+
+    private fun getAutoClosingRoomOpenHelperFactory(
+        timeoutMillis: Long = 10
+    ): AutoClosingRoomOpenHelperFactory {
+        val delegateOpenHelperFactory = FrameworkSQLiteOpenHelperFactory()
+
+        return AutoClosingRoomOpenHelperFactory(
+            delegateOpenHelperFactory,
+            AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, Executors.newSingleThreadExecutor())
+        )
+    }
+
+    @Test
+    public fun testCallbacksCalled() {
+        val autoClosingRoomOpenHelperFactory =
+            getAutoClosingRoomOpenHelperFactory()
+
+        val callbackCount = AtomicInteger()
+
+        val countingCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+            override fun onCreate(db: SupportSQLiteDatabase) {
+                callbackCount.incrementAndGet()
+            }
+
+            override fun onConfigure(db: SupportSQLiteDatabase) {
+                callbackCount.incrementAndGet()
+            }
+
+            override fun onOpen(db: SupportSQLiteDatabase) {
+                callbackCount.incrementAndGet()
+            }
+
+            override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+            }
+        }
+
+        val autoClosingRoomOpenHelper = autoClosingRoomOpenHelperFactory.create(
+            SupportSQLiteOpenHelper.Configuration
+                .builder(ApplicationProvider.getApplicationContext())
+                .callback(countingCallback)
+                .name(DB_NAME)
+                .build()
+        )
+
+        autoClosingRoomOpenHelper.writableDatabase
+
+        assertEquals(3, callbackCount.get())
+        Thread.sleep(100)
+
+        autoClosingRoomOpenHelper.writableDatabase
+        assertEquals(5, callbackCount.get()) // onCreate won't be called the second time.
+    }
+
+    @Test
+    public fun testDatabaseIsOpenForSlowCallbacks() {
+        val autoClosingRoomOpenHelperFactory =
+            getAutoClosingRoomOpenHelperFactory()
+
+        val refCountCheckingCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+            override fun onCreate(db: SupportSQLiteDatabase) {
+                Thread.sleep(100)
+                db.execSQL("create table user (idk int)")
+            }
+
+            override fun onConfigure(db: SupportSQLiteDatabase) {
+                Thread.sleep(100)
+                db.maximumSize = 100000
+            }
+
+            override fun onOpen(db: SupportSQLiteDatabase) {
+                Thread.sleep(100)
+                db.execSQL("select * from user")
+            }
+
+            override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+            }
+        }
+
+        val autoClosingRoomOpenHelper = autoClosingRoomOpenHelperFactory.create(
+            SupportSQLiteOpenHelper.Configuration
+                .builder(ApplicationProvider.getApplicationContext())
+                .callback(refCountCheckingCallback)
+                .name(DB_NAME)
+                .build()
+        )
+
+        val db = autoClosingRoomOpenHelper.writableDatabase
+        assertTrue(db.isOpen)
+    }
+}
\ No newline at end of file
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
new file mode 100644
index 0000000..e19a845
--- /dev/null
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import android.content.Context
+import android.database.sqlite.SQLiteException
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.core.app.ApplicationProvider
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import java.io.IOException
+import java.lang.UnsupportedOperationException
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+public class AutoClosingRoomOpenHelperTest {
+
+    private open class Callback(var throwOnOpen: Boolean = false) :
+        SupportSQLiteOpenHelper.Callback(1) {
+        override fun onCreate(db: SupportSQLiteDatabase) {}
+
+        override fun onOpen(db: SupportSQLiteDatabase) {
+            if (throwOnOpen) {
+                throw IOException()
+            }
+        }
+
+        override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+    }
+
+    @Before
+    public fun setUp() {
+        ApplicationProvider.getApplicationContext<Context>().deleteDatabase("name")
+    }
+
+    private fun getAutoClosingRoomOpenHelper(
+        timeoutMillis: Long = 10,
+        callback: SupportSQLiteOpenHelper.Callback = Callback()
+    ): AutoClosingRoomOpenHelper {
+
+        val delegateOpenHelper = FrameworkSQLiteOpenHelperFactory()
+            .create(
+                SupportSQLiteOpenHelper.Configuration
+                    .builder(ApplicationProvider.getApplicationContext())
+                    .callback(callback)
+                    .name("name")
+                    .build()
+            )
+
+        val autoCloseExecutor = Executors.newSingleThreadExecutor()
+
+        return AutoClosingRoomOpenHelper(
+            delegateOpenHelper,
+            AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, autoCloseExecutor)
+        )
+    }
+
+    @Test
+    public fun testQueryFailureDecrementsRefCount() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+
+        assertThrows<SQLiteException> {
+            autoClosingRoomOpenHelper
+                .writableDatabase.query("select * from nonexistanttable")
+        }
+
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
+    }
+
+    @Test
+    public fun testCursorKeepsDbAlive() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+        autoClosingRoomOpenHelper.writableDatabase.execSQL("create table user (idk int)")
+
+        val cursor =
+            autoClosingRoomOpenHelper.writableDatabase.query("select * from user")
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(1)
+        cursor.close()
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
+    }
+
+    @Test
+    public fun testTransactionKeepsDbAlive() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+        autoClosingRoomOpenHelper.writableDatabase.beginTransaction()
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(1)
+        autoClosingRoomOpenHelper.writableDatabase.endTransaction()
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
+    }
+
+    @Test
+    public fun enableWriteAheadLogging_onOpenHelper() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+
+        autoClosingRoomOpenHelper.setWriteAheadLoggingEnabled(true)
+        assertThat(autoClosingRoomOpenHelper.writableDatabase.isWriteAheadLoggingEnabled).isTrue()
+
+        Thread.sleep(100) // Let the db auto close...
+
+        assertThat(autoClosingRoomOpenHelper.writableDatabase.isWriteAheadLoggingEnabled).isTrue()
+    }
+
+    @Test
+    public fun testEnableWriteAheadLogging_onSupportSqliteDatabase_throwsUnsupportedOperation() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+
+        assertThrows<UnsupportedOperationException> {
+            autoClosingRoomOpenHelper.writableDatabase.enableWriteAheadLogging()
+        }
+
+        assertThrows<UnsupportedOperationException> {
+            autoClosingRoomOpenHelper.writableDatabase.disableWriteAheadLogging()
+        }
+    }
+
+    @Test
+    public fun testOnOpenCalledOnEachOpen() {
+        val countingCallback = object : Callback() {
+            var onCreateCalls = 0
+            var onOpenCalls = 0
+
+            override fun onCreate(db: SupportSQLiteDatabase) {
+                onCreateCalls++
+            }
+
+            override fun onOpen(db: SupportSQLiteDatabase) {
+                super.onOpen(db)
+                onOpenCalls++
+            }
+        }
+
+        val autoClosingRoomOpenHelper =
+            getAutoClosingRoomOpenHelper(callback = countingCallback)
+
+        autoClosingRoomOpenHelper.writableDatabase
+        assertThat(countingCallback.onOpenCalls).isEqualTo(1)
+        assertThat(countingCallback.onCreateCalls).isEqualTo(1)
+
+        Thread.sleep(20) // Database should auto-close here
+        autoClosingRoomOpenHelper.writableDatabase
+        assertThat(countingCallback.onOpenCalls).isEqualTo(2)
+        assertThat(countingCallback.onCreateCalls).isEqualTo(1)
+    }
+
+    @Test
+    public fun testStatementReturnedByCompileStatement_doesntKeepDatabaseOpen() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+
+        val db = autoClosingRoomOpenHelper.writableDatabase
+        db.execSQL("create table user (idk int)")
+
+        db.compileStatement("insert into users (idk) values (1)")
+
+        Thread.sleep(20)
+        assertThat(db.isOpen).isFalse() // db should close
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
+    }
+
+    @Test
+    public fun testStatementReturnedByCompileStatement_reOpensDatabase() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+
+        val db = autoClosingRoomOpenHelper.writableDatabase
+        db.execSQL("create table user (idk int)")
+
+        val statement = db
+            .compileStatement("insert into user (idk) values (1)")
+
+        Thread.sleep(20)
+
+        statement.executeInsert() // This should succeed
+
+        db.query("select * from user").use {
+            assertThat(it.count).isEqualTo(1)
+        }
+
+        assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
+    }
+
+    @Test
+    public fun testStatementReturnedByCompileStatement_worksWithBinds() {
+        val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
+        val db = autoClosingRoomOpenHelper.writableDatabase
+
+        db.execSQL("create table users (i int, d double, b blob, n int, s string)")
+
+        val statement = db.compileStatement(
+            "insert into users (i, d, b, n, s) values (?,?,?,?,?)"
+        )
+
+        statement.bindString(5, "123")
+        statement.bindLong(1, 123)
+        statement.bindDouble(2, 1.23)
+        statement.bindBlob(3, byteArrayOf(1, 2, 3))
+        statement.bindNull(4)
+
+        statement.executeInsert()
+
+        db.query("select * from users").use {
+            assertThat(it.moveToFirst()).isTrue()
+            assertThat(it.getInt(0)).isEqualTo(123)
+            assertThat(it.getDouble(1)).isWithin(.01).of(1.23)
+
+            assertThat(it.getBlob(2)).isEqualTo(byteArrayOf(1, 2, 3))
+            assertThat(it.isNull(3)).isTrue()
+            assertThat(it.getString(4)).isEqualTo("123")
+        }
+
+        statement.clearBindings()
+        statement.executeInsert() // should insert with nulls
+
+        db.query("select * from users").use {
+            assertThat(it.moveToFirst()).isTrue()
+            it.moveToNext()
+            assertThat(it.isNull(0)).isTrue()
+            assertThat(it.isNull(1)).isTrue()
+            assertThat(it.isNull(2)).isTrue()
+            assertThat(it.isNull(3)).isTrue()
+            assertThat(it.isNull(4)).isTrue()
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java
new file mode 100644
index 0000000..87d364f
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java
@@ -0,0 +1,868 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.arch.core.util.Function;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A SupportSQLiteOpenHelper that has autoclose enabled for database connections.
+ */
+final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper {
+    @NonNull
+    private final SupportSQLiteOpenHelper mDelegateOpenHelper;
+
+    @NonNull
+    private final AutoClosingSupportSQLiteDatabase mAutoClosingDb;
+
+    @NonNull
+    private final AutoCloser mAutoCloser;
+
+    AutoClosingRoomOpenHelper(@NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper,
+            @NonNull AutoCloser autoCloser) {
+        mDelegateOpenHelper = supportSQLiteOpenHelper;
+        mAutoCloser = autoCloser;
+        autoCloser.init(mDelegateOpenHelper);
+        mAutoClosingDb = new AutoClosingSupportSQLiteDatabase(mAutoCloser);
+    }
+
+    @Nullable
+    @Override
+    public String getDatabaseName() {
+        return mDelegateOpenHelper.getDatabaseName();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setWriteAheadLoggingEnabled(boolean enabled) {
+        mDelegateOpenHelper.setWriteAheadLoggingEnabled(enabled);
+    }
+
+    @NonNull
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @Override
+    public SupportSQLiteDatabase getWritableDatabase() {
+        // Note we don't differentiate between writable db and readable db
+        // We try to open the db so the open callbacks run
+        mAutoClosingDb.pokeOpen();
+        return mAutoClosingDb;
+    }
+
+    @NonNull
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @Override
+    public SupportSQLiteDatabase getReadableDatabase() {
+        // Note we don't differentiate between writable db and readable db
+        // We try to open the db so the open callbacks run
+        mAutoClosingDb.pokeOpen();
+        return mAutoClosingDb;
+    }
+
+    @Override
+    public void close() {
+        mAutoClosingDb.close();
+    }
+
+    /**
+     * package protected to pass it to invalidation tracker...
+     */
+    @NonNull
+    AutoCloser getAutoCloser() {
+        return this.mAutoCloser;
+    }
+
+    @NonNull
+    SupportSQLiteDatabase getAutoClosingDb() {
+        return this.mAutoClosingDb;
+    }
+
+    /**
+     * SupportSQLiteDatabase that also keeps refcounts and autocloses the database
+     */
+    static final class AutoClosingSupportSQLiteDatabase implements SupportSQLiteDatabase {
+        @NonNull
+        private final AutoCloser mAutoCloser;
+
+        AutoClosingSupportSQLiteDatabase(@NonNull AutoCloser autoCloser) {
+            mAutoCloser = autoCloser;
+        }
+
+        void pokeOpen() {
+            mAutoCloser.executeRefCountingFunction(db -> null);
+        }
+
+        @Override
+        public SupportSQLiteStatement compileStatement(String sql) {
+            return new AutoClosingSupportSqliteStatement(sql, mAutoCloser);
+        }
+
+        @Override
+        public void beginTransaction() {
+            // We assume that after every successful beginTransaction() call there *must* be a
+            // endTransaction() call.
+            SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+            try {
+                db.beginTransaction();
+            } catch (Throwable t) {
+                // Note: we only want to decrement the ref count if the beginTransaction call
+                // fails since there won't be a corresponding endTransaction call.
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw t;
+            }
+        }
+
+        @Override
+        public void beginTransactionNonExclusive() {
+            // We assume that after every successful beginTransaction() call there *must* be a
+            // endTransaction() call.
+            SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+            try {
+                db.beginTransactionNonExclusive();
+            } catch (Throwable t) {
+                // Note: we only want to decrement the ref count if the beginTransaction call
+                // fails since there won't be a corresponding endTransaction call.
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw t;
+            }
+        }
+
+        @Override
+        public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+            // We assume that after every successful beginTransaction() call there *must* be a
+            // endTransaction() call.
+            SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+            try {
+                db.beginTransactionWithListener(transactionListener);
+            } catch (Throwable t) {
+                // Note: we only want to decrement the ref count if the beginTransaction call
+                // fails since there won't be a corresponding endTransaction call.
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw t;
+            }
+        }
+
+        @Override
+        public void beginTransactionWithListenerNonExclusive(
+                SQLiteTransactionListener transactionListener) {
+            // We assume that after every successful beginTransaction() call there *will* always
+            // be a corresponding endTransaction() call. Without a corresponding
+            // endTransactionCall we will never close the db.
+            SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+            try {
+                db.beginTransactionWithListenerNonExclusive(transactionListener);
+            } catch (Throwable t) {
+                // Note: we only want to decrement the ref count if the beginTransaction call
+                // fails since there won't be a corresponding endTransaction call.
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw t;
+            }
+        }
+
+        @Override
+        public void endTransaction() {
+            if (mAutoCloser.getDelegateDatabase() == null) {
+                // This should never happen.
+                throw new IllegalStateException("End transaction called but delegateDb is null");
+            }
+
+            try {
+                mAutoCloser.getDelegateDatabase().endTransaction();
+            } finally {
+                mAutoCloser.decrementCountAndScheduleClose();
+            }
+        }
+
+        @Override
+        public void setTransactionSuccessful() {
+            SupportSQLiteDatabase delegate = mAutoCloser.getDelegateDatabase();
+
+            if (delegate == null) {
+                // This should never happen.
+                throw new IllegalStateException("setTransactionSuccessful called but delegateDb "
+                        + "is null");
+            }
+
+            delegate.setTransactionSuccessful();
+        }
+
+        @Override
+        public boolean inTransaction() {
+            if (mAutoCloser.getDelegateDatabase() == null) {
+                return false;
+            }
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::inTransaction);
+        }
+
+        @Override
+        public boolean isDbLockedByCurrentThread() {
+            if (mAutoCloser.getDelegateDatabase() == null) {
+                return false;
+            }
+
+            return mAutoCloser.executeRefCountingFunction(
+                    SupportSQLiteDatabase::isDbLockedByCurrentThread);
+        }
+
+        @Override
+        public boolean yieldIfContendedSafely() {
+            return mAutoCloser.executeRefCountingFunction(
+                    SupportSQLiteDatabase::yieldIfContendedSafely);
+        }
+
+        @Override
+        public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
+            return mAutoCloser.executeRefCountingFunction(
+                    SupportSQLiteDatabase::yieldIfContendedSafely);
+
+        }
+
+        @Override
+        public int getVersion() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getVersion);
+        }
+
+        @Override
+        public void setVersion(int version) {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.setVersion(version);
+                return null;
+            });
+        }
+
+        @Override
+        public long getMaximumSize() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getMaximumSize);
+        }
+
+        @Override
+        public long setMaximumSize(long numBytes) {
+            return mAutoCloser.executeRefCountingFunction(db -> db.setMaximumSize(numBytes));
+        }
+
+        @Override
+        public long getPageSize() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getPageSize);
+        }
+
+        @Override
+        public void setPageSize(long numBytes) {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.setPageSize(numBytes);
+                return null;
+            });
+        }
+
+        @Override
+        public Cursor query(String query) {
+            Cursor result;
+            try {
+                SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+                result = db.query(query);
+            } catch (Throwable throwable) {
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw throwable;
+            }
+
+            return new KeepAliveCursor(result, mAutoCloser);
+        }
+
+        @Override
+        public Cursor query(String query, Object[] bindArgs) {
+            Cursor result;
+            try {
+                SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+                result = db.query(query, bindArgs);
+            } catch (Throwable throwable) {
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw throwable;
+            }
+
+            return new KeepAliveCursor(result, mAutoCloser);
+        }
+
+        @Override
+        public Cursor query(SupportSQLiteQuery query) {
+
+            Cursor result;
+            try {
+                SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+                result = db.query(query);
+            } catch (Throwable throwable) {
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw throwable;
+            }
+
+            return new KeepAliveCursor(result, mAutoCloser);
+        }
+
+        @RequiresApi(api = Build.VERSION_CODES.N)
+        @Override
+        public Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal) {
+            Cursor result;
+            try {
+                SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
+                result = db.query(query, cancellationSignal);
+            } catch (Throwable throwable) {
+                mAutoCloser.decrementCountAndScheduleClose();
+                throw throwable;
+            }
+
+            return new KeepAliveCursor(result, mAutoCloser);
+        }
+
+        @Override
+        public long insert(String table, int conflictAlgorithm, ContentValues values)
+                throws SQLException {
+            return mAutoCloser.executeRefCountingFunction(db -> db.insert(table, conflictAlgorithm,
+                    values));
+        }
+
+        @Override
+        public int delete(String table, String whereClause, Object[] whereArgs) {
+            return mAutoCloser.executeRefCountingFunction(
+                    db -> db.delete(table, whereClause, whereArgs));
+        }
+
+        @Override
+        public int update(String table, int conflictAlgorithm, ContentValues values,
+                String whereClause, Object[] whereArgs) {
+            return mAutoCloser.executeRefCountingFunction(db -> db.update(table, conflictAlgorithm,
+                    values, whereClause, whereArgs));
+        }
+
+        @Override
+        public void execSQL(String sql) throws SQLException {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.execSQL(sql);
+                return null;
+            });
+        }
+
+        @Override
+        public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.execSQL(sql, bindArgs);
+                return null;
+            });
+        }
+
+        @Override
+        public boolean isReadOnly() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::isReadOnly);
+        }
+
+        @Override
+        public boolean isOpen() {
+            // Get the db without incrementing the reference cause we don't want to open
+            // the db for an isOpen call.
+            SupportSQLiteDatabase localDelegate = mAutoCloser.getDelegateDatabase();
+
+            if (localDelegate == null) {
+                return false;
+            }
+            return localDelegate.isOpen();
+        }
+
+        @Override
+        public boolean needUpgrade(int newVersion) {
+            return mAutoCloser.executeRefCountingFunction(db -> db.needUpgrade(newVersion));
+        }
+
+        @Override
+        public String getPath() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getPath);
+        }
+
+        @Override
+        public void setLocale(Locale locale) {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.setLocale(locale);
+                return null;
+            });
+        }
+
+        @Override
+        public void setMaxSqlCacheSize(int cacheSize) {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                db.setMaxSqlCacheSize(cacheSize);
+                return null;
+            });
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+        @Override
+        public void setForeignKeyConstraintsEnabled(boolean enable) {
+            mAutoCloser.executeRefCountingFunction(db -> {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    db.setForeignKeyConstraintsEnabled(enable);
+                }
+                return null;
+            });
+        }
+
+        @Override
+        public boolean enableWriteAheadLogging() {
+            throw new UnsupportedOperationException("Enable/disable write ahead logging on the "
+                    + "OpenHelper instead of on the database directly.");
+        }
+
+        @Override
+        public void disableWriteAheadLogging() {
+            throw new UnsupportedOperationException("Enable/disable write ahead logging on the "
+                    + "OpenHelper instead of on the database directly.");
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+        @Override
+        public boolean isWriteAheadLoggingEnabled() {
+            return mAutoCloser.executeRefCountingFunction(db -> {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    return db.isWriteAheadLoggingEnabled();
+                }
+                return false;
+            });
+        }
+
+        @Override
+        public List<Pair<String, String>> getAttachedDbs() {
+            return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getAttachedDbs);
+        }
+
+        @Override
+        public boolean isDatabaseIntegrityOk() {
+            return mAutoCloser.executeRefCountingFunction(
+                    SupportSQLiteDatabase::isDatabaseIntegrityOk);
+        }
+
+        @Override
+        public void close() {
+            //TODO: I might want to handle a manual close call here differently because as it is
+            // implemented right now, if someone calls close() then calls another method here,
+            // it'll automatically reopen the db, which may not be expected. However, having a
+            // single instance here makes this simpler.
+            throw new IllegalStateException("can't be manually closed yet");
+        }
+    }
+
+    /**
+     * We need to keep the db alive until the cursor is closed, so we can't decrement our
+     * reference count until the cursor is closed. The underlying database will not close until
+     * this cursor is closed.
+     */
+    private static final class KeepAliveCursor implements Cursor {
+        private final Cursor mDelegate;
+        private final AutoCloser mAutoCloser;
+
+        KeepAliveCursor(Cursor delegate, AutoCloser autoCloser) {
+            mDelegate = delegate;
+            mAutoCloser = autoCloser;
+        }
+
+        // close is the only important/changed method here:
+        @Override
+        public void close() {
+            mDelegate.close();
+            mAutoCloser.decrementCountAndScheduleClose();
+        }
+
+        @Override
+        public boolean isClosed() {
+            return mDelegate.isClosed();
+        }
+
+
+        @Override
+        public int getCount() {
+            return mDelegate.getCount();
+        }
+
+        @Override
+        public int getPosition() {
+            return mDelegate.getPosition();
+        }
+
+        @Override
+        public boolean move(int offset) {
+            return mDelegate.move(offset);
+        }
+
+        @Override
+        public boolean moveToPosition(int position) {
+            return mDelegate.moveToPosition(position);
+        }
+
+        @Override
+        public boolean moveToFirst() {
+            return mDelegate.moveToFirst();
+        }
+
+        @Override
+        public boolean moveToLast() {
+            return mDelegate.moveToLast();
+        }
+
+        @Override
+        public boolean moveToNext() {
+            return mDelegate.moveToNext();
+        }
+
+        @Override
+        public boolean moveToPrevious() {
+            return mDelegate.moveToPrevious();
+        }
+
+        @Override
+        public boolean isFirst() {
+            return mDelegate.isFirst();
+        }
+
+        @Override
+        public boolean isLast() {
+            return mDelegate.isLast();
+        }
+
+        @Override
+        public boolean isBeforeFirst() {
+            return mDelegate.isBeforeFirst();
+        }
+
+        @Override
+        public boolean isAfterLast() {
+            return mDelegate.isAfterLast();
+        }
+
+        @Override
+        public int getColumnIndex(String columnName) {
+            return mDelegate.getColumnIndex(columnName);
+        }
+
+        @Override
+        public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
+            return mDelegate.getColumnIndexOrThrow(columnName);
+        }
+
+        @Override
+        public String getColumnName(int columnIndex) {
+            return mDelegate.getColumnName(columnIndex);
+        }
+
+        @Override
+        public String[] getColumnNames() {
+            return mDelegate.getColumnNames();
+        }
+
+        @Override
+        public int getColumnCount() {
+            return mDelegate.getColumnCount();
+        }
+
+        @Override
+        public byte[] getBlob(int columnIndex) {
+            return mDelegate.getBlob(columnIndex);
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            return mDelegate.getString(columnIndex);
+        }
+
+        @Override
+        public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+            mDelegate.copyStringToBuffer(columnIndex, buffer);
+        }
+
+        @Override
+        public short getShort(int columnIndex) {
+            return mDelegate.getShort(columnIndex);
+        }
+
+        @Override
+        public int getInt(int columnIndex) {
+            return mDelegate.getInt(columnIndex);
+        }
+
+        @Override
+        public long getLong(int columnIndex) {
+            return mDelegate.getLong(columnIndex);
+        }
+
+        @Override
+        public float getFloat(int columnIndex) {
+            return mDelegate.getFloat(columnIndex);
+        }
+
+        @Override
+        public double getDouble(int columnIndex) {
+            return mDelegate.getDouble(columnIndex);
+        }
+
+        @Override
+        public int getType(int columnIndex) {
+            return mDelegate.getType(columnIndex);
+        }
+
+        @Override
+        public boolean isNull(int columnIndex) {
+            return mDelegate.isNull(columnIndex);
+        }
+
+        /**
+         * @deprecated see Cursor.deactivate
+         */
+        @Override
+        @Deprecated
+        public void deactivate() {
+            mDelegate.deactivate();
+        }
+
+        /**
+         * @deprecated see Cursor.requery
+         */
+        @Override
+        @Deprecated
+        public boolean requery() {
+            return mDelegate.requery();
+        }
+
+        @Override
+        public void registerContentObserver(ContentObserver observer) {
+            mDelegate.registerContentObserver(observer);
+        }
+
+        @Override
+        public void unregisterContentObserver(ContentObserver observer) {
+            mDelegate.unregisterContentObserver(observer);
+        }
+
+        @Override
+        public void registerDataSetObserver(DataSetObserver observer) {
+            mDelegate.registerDataSetObserver(observer);
+        }
+
+        @Override
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            mDelegate.unregisterDataSetObserver(observer);
+        }
+
+        @Override
+        public void setNotificationUri(ContentResolver cr, Uri uri) {
+            mDelegate.setNotificationUri(cr, uri);
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.Q)
+        @Override
+        public void setNotificationUris(@NonNull ContentResolver cr,
+                @NonNull List<Uri> uris) {
+            mDelegate.setNotificationUris(cr, uris);
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+        @Override
+        public Uri getNotificationUri() {
+            return mDelegate.getNotificationUri();
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.Q)
+        @Nullable
+        @Override
+        public List<Uri> getNotificationUris() {
+            return mDelegate.getNotificationUris();
+        }
+
+        @Override
+        public boolean getWantsAllOnMoveCalls() {
+            return mDelegate.getWantsAllOnMoveCalls();
+        }
+
+        @SuppressLint("UnsafeNewApiCall")
+        @RequiresApi(api = Build.VERSION_CODES.M)
+        @Override
+        public void setExtras(Bundle extras) {
+            mDelegate.setExtras(extras);
+        }
+
+        @Override
+        public Bundle getExtras() {
+            return mDelegate.getExtras();
+        }
+
+        @Override
+        public Bundle respond(Bundle extras) {
+            return mDelegate.respond(extras);
+        }
+    }
+
+    /**
+     * We can't close our db if the SupportSqliteStatement is open.
+     *
+     * Each of these that are created need to be registered with RefCounter.
+     *
+     * On auto-close, RefCounter needs to close each of these before closing the db that these
+     * were constructed from.
+     *
+     * Each of the methods here need to get
+     */
+    //TODO(rohitsat) cache the prepared statement... I'm not sure what the performance implications
+    // are for the way it's done here, but caching the prepared statement would definitely be more
+    // complicated since we need to invalidate any of the PreparedStatements that were created
+    // with this db
+    private static class AutoClosingSupportSqliteStatement implements SupportSQLiteStatement {
+        private final String mSql;
+        private final ArrayList<Object> mBinds = new ArrayList<>();
+        private final AutoCloser mAutoCloser;
+
+        AutoClosingSupportSqliteStatement(
+                String sql, AutoCloser autoCloser) {
+            mSql = sql;
+            mAutoCloser = autoCloser;
+        }
+
+        private <T> T executeSqliteStatementWithRefCount(Function<SupportSQLiteStatement, T> func) {
+            return mAutoCloser.executeRefCountingFunction(
+                    db -> {
+                        SupportSQLiteStatement statement = db.compileStatement(mSql);
+                        doBinds(statement);
+                        return func.apply(statement);
+                    }
+            );
+        }
+
+        private void doBinds(SupportSQLiteStatement supportSQLiteStatement) {
+            // Replay the binds
+            for (int i = 0; i < mBinds.size(); i++) {
+                int bindIndex = i + 1; // Bind indices are 1 based so we start at 1 not 0
+                Object bind = mBinds.get(i);
+                if (bind == null) {
+                    supportSQLiteStatement.bindNull(bindIndex);
+                } else if (bind instanceof Long) {
+                    supportSQLiteStatement.bindLong(bindIndex, (Long) bind);
+                } else if (bind instanceof Double) {
+                    supportSQLiteStatement.bindDouble(bindIndex, (Double) bind);
+                } else if (bind instanceof String) {
+                    supportSQLiteStatement.bindString(bindIndex, (String) bind);
+                } else if (bind instanceof byte[]) {
+                    supportSQLiteStatement.bindBlob(bindIndex, (byte[]) bind);
+                }
+            }
+        }
+
+        private void saveBinds(int bindIndex, Object value) {
+            int index = bindIndex - 1;
+            if (index >= mBinds.size()) {
+                // Add null entries to the list until we have the desired # of indices
+                for (int i = mBinds.size(); i <= index; i++) {
+                    mBinds.add(null);
+                }
+            }
+            mBinds.set(index, value);
+        }
+
+        @Override
+        public void close() throws IOException {
+            // Nothing to do here since we re-compile the statement each time.
+        }
+
+        @Override
+        public void execute() {
+            executeSqliteStatementWithRefCount(statement -> {
+                statement.execute();
+                return null;
+            });
+        }
+
+        @Override
+        public int executeUpdateDelete() {
+            return executeSqliteStatementWithRefCount(SupportSQLiteStatement::executeUpdateDelete);
+        }
+
+        @Override
+        public long executeInsert() {
+            return executeSqliteStatementWithRefCount(SupportSQLiteStatement::executeInsert);
+        }
+
+        @Override
+        public long simpleQueryForLong() {
+            return executeSqliteStatementWithRefCount(SupportSQLiteStatement::simpleQueryForLong);
+        }
+
+        @Override
+        public String simpleQueryForString() {
+            return executeSqliteStatementWithRefCount(SupportSQLiteStatement::simpleQueryForString);
+        }
+
+        @Override
+        public void bindNull(int index) {
+            saveBinds(index, null);
+        }
+
+        @Override
+        public void bindLong(int index, long value) {
+            saveBinds(index, value);
+        }
+
+        @Override
+        public void bindDouble(int index, double value) {
+            saveBinds(index, value);
+        }
+
+        @Override
+        public void bindString(int index, String value) {
+            saveBinds(index, value);
+        }
+
+        @Override
+        public void bindBlob(int index, byte[] value) {
+            saveBinds(index, value);
+        }
+
+        @Override
+        public void clearBindings() {
+            mBinds.clear();
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelperFactory.java b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelperFactory.java
new file mode 100644
index 0000000..004f60a
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelperFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.NonNull;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+/**
+ * Factory class for AutoClosingRoomOpenHelper
+ */
+final class AutoClosingRoomOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+    @NonNull
+    private final SupportSQLiteOpenHelper.Factory mDelegate;
+
+    @NonNull
+    private final AutoCloser mAutoCloser;
+
+    AutoClosingRoomOpenHelperFactory(
+            @NonNull SupportSQLiteOpenHelper.Factory factory,
+            @NonNull AutoCloser autoCloser) {
+        mDelegate = factory;
+        mAutoCloser = autoCloser;
+    }
+
+    /**
+     * @return AutoClosingRoomOpenHelper instances.
+     */
+    @Override
+    @NonNull
+    public AutoClosingRoomOpenHelper create(
+            @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
+        return new AutoClosingRoomOpenHelper(mDelegate.create(configuration), mAutoCloser);
+    }
+}