Add the ability to use the no backup directory for creating the database.

* Configuration.Builder exposes a new API `noBackupDirectory`.

Fixes: b/142681857
Test: Added unit tests. Integration tests pass. Ran ./gradlew bOS
Change-Id: I61d3d4a75c214665b5c39421411bd3f6f5f4562c
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/NoBackupDirectoryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/NoBackupDirectoryTest.java
new file mode 100644
index 0000000..ace1db1
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/NoBackupDirectoryTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 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.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NoBackupDirectoryTest {
+
+    private static final String NAME = "database.db";
+
+    private Context mContext;
+    private SupportSQLiteOpenHelper.Factory mFactory;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mFactory = new AlwaysNoBackupFactory();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 23)
+    public void testBuildDatabase_withCustomSupportSQLiteOpenHelper() {
+        // Setting the minSdkVersion to 23 even though Context.getNoBackupFilesDir() is supported
+        // on API 21+ because it was unused until API 23.
+        RoomDatabase database = Room.databaseBuilder(mContext, TestDatabase.class, NAME)
+                .openHelperFactory(mFactory)
+                .build();
+
+        SupportSQLiteOpenHelper helper = database.getOpenHelper();
+        assertThat(helper.getDatabaseName(), is(NAME));
+        SupportSQLiteDatabase sqLiteDatabase = helper.getWritableDatabase();
+        String expectedPath = new File(mContext.getNoBackupFilesDir(), NAME).getPath();
+        assertThat(sqLiteDatabase.getPath(), is(expectedPath));
+        database.close();
+    }
+
+    @Test
+    public void testBuildInMemoryDatabase_withAlwaysNoBackupFactory() {
+        Throwable exception = null;
+        try {
+            RoomDatabase database = Room.inMemoryDatabaseBuilder(mContext, TestDatabase.class)
+                    .openHelperFactory(mFactory)
+                    .build();
+            database.getOpenHelper().getWritableDatabase();
+        } catch (Throwable expected) {
+            exception = expected;
+        }
+
+        assertThat(exception, notNullValue());
+        assertThat(exception.getMessage(),
+                is("Must set a non-null database name to a configuration that uses the no backup "
+                        + "directory."));
+    }
+
+    static class AlwaysNoBackupFactory implements SupportSQLiteOpenHelper.Factory {
+        private final SupportSQLiteOpenHelper.Factory mDelegate;
+
+        AlwaysNoBackupFactory() {
+            mDelegate = new FrameworkSQLiteOpenHelperFactory();
+        }
+
+        @NonNull
+        @Override
+        public SupportSQLiteOpenHelper create(
+                @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
+            SupportSQLiteOpenHelper.Configuration backupConfiguration =
+                    SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
+                            .callback(configuration.callback)
+                            .name(configuration.name)
+                            .noBackupDirectory(true)
+                            .build();
+            return mDelegate.create(backupConfiguration);
+        }
+    }
+}
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 113582b..0f271d7 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -34,8 +34,8 @@
 
 dependencies {
     api(project(":room:room-common"))
-    api("androidx.sqlite:sqlite-framework:2.0.1")
-    api("androidx.sqlite:sqlite:2.0.1")
+    api(project(":sqlite:sqlite-framework"))
+    api(project(":sqlite:sqlite"))
     implementation("androidx.arch.core:core-runtime:2.0.1")
     compileOnly("androidx.paging:paging-common:2.0.0")
     compileOnly("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
index 3275857..a457b14 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
@@ -25,42 +25,95 @@
 import androidx.sqlite.db.SupportSQLiteDatabase;
 import androidx.sqlite.db.SupportSQLiteOpenHelper;
 
-class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
-    private final OpenHelper mDelegate;
+import java.io.File;
 
-    FrameworkSQLiteOpenHelper(Context context, String name, Callback callback) {
-        mDelegate = createDelegate(context, name, callback);
+class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
+
+    private final Context mContext;
+    private final String mName;
+    private final Callback mCallback;
+    private final boolean mUseNoBackupDirectory;
+    private final Object mLock;
+
+    // Delegate is created lazily
+    private OpenHelper mDelegate;
+    private boolean mWriteAheadLoggingEnabled;
+
+    FrameworkSQLiteOpenHelper(
+            Context context,
+            String name,
+            Callback callback) {
+        this(context, name, callback, false);
     }
 
-    private OpenHelper createDelegate(Context context, String name, Callback callback) {
-        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
-        return new OpenHelper(context, name, dbRef, callback);
+    FrameworkSQLiteOpenHelper(
+            Context context,
+            String name,
+            Callback callback,
+            boolean useNoBackupDirectory) {
+        mContext = context;
+        mName = name;
+        mCallback = callback;
+        mUseNoBackupDirectory = useNoBackupDirectory;
+        mLock = new Object();
+    }
+
+    private OpenHelper getDelegate() {
+        // getDelegate() is lazy because we don't want to File I/O until the call to
+        // getReadableDatabase() or getWritableDatabase(). This is better because the call to
+        // a getReadableDatabase() or a getWritableDatabase() happens on a background thread unless
+        // queries are allowed on the main thread.
+
+        // We defer computing the path the database from the constructor to getDelegate()
+        // because context.getNoBackupFilesDir() does File I/O :(
+        synchronized (mLock) {
+            if (mDelegate == null) {
+                final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                        && mName != null
+                        && mUseNoBackupDirectory) {
+                    File file = new File(mContext.getNoBackupFilesDir(), mName);
+                    mDelegate = new OpenHelper(mContext, file.getAbsolutePath(), dbRef, mCallback);
+                } else {
+                    mDelegate = new OpenHelper(mContext, mName, dbRef, mCallback);
+                }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    mDelegate.setWriteAheadLoggingEnabled(mWriteAheadLoggingEnabled);
+                }
+            }
+            return mDelegate;
+        }
     }
 
     @Override
     public String getDatabaseName() {
-        return mDelegate.getDatabaseName();
+        return mName;
     }
 
     @Override
     @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
     public void setWriteAheadLoggingEnabled(boolean enabled) {
-        mDelegate.setWriteAheadLoggingEnabled(enabled);
+        synchronized (mLock) {
+            if (mDelegate != null) {
+                mDelegate.setWriteAheadLoggingEnabled(enabled);
+            }
+            mWriteAheadLoggingEnabled = enabled;
+        }
     }
 
     @Override
     public SupportSQLiteDatabase getWritableDatabase() {
-        return mDelegate.getWritableSupportDatabase();
+        return getDelegate().getWritableSupportDatabase();
     }
 
     @Override
     public SupportSQLiteDatabase getReadableDatabase() {
-        return mDelegate.getReadableSupportDatabase();
+        return getDelegate().getReadableSupportDatabase();
     }
 
     @Override
     public void close() {
-        mDelegate.close();
+        getDelegate().close();
     }
 
     static class OpenHelper extends SQLiteOpenHelper {
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
index d27abee..2f55202 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -30,6 +30,9 @@
     public SupportSQLiteOpenHelper create(
             @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
         return new FrameworkSQLiteOpenHelper(
-                configuration.context, configuration.name, configuration.callback);
+                configuration.context,
+                configuration.name,
+                configuration.callback,
+                configuration.useNoBackupDirectory);
     }
 }
diff --git a/sqlite/sqlite/api/2.1.0-alpha01.txt b/sqlite/sqlite/api/2.1.0-alpha01.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/2.1.0-alpha01.txt
+++ b/sqlite/sqlite/api/2.1.0-alpha01.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/api/current.txt b/sqlite/sqlite/api/current.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/current.txt
+++ b/sqlite/sqlite/api/current.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/api/public_plus_experimental_2.1.0-alpha01.txt b/sqlite/sqlite/api/public_plus_experimental_2.1.0-alpha01.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/public_plus_experimental_2.1.0-alpha01.txt
+++ b/sqlite/sqlite/api/public_plus_experimental_2.1.0-alpha01.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/api/public_plus_experimental_current.txt b/sqlite/sqlite/api/public_plus_experimental_current.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/public_plus_experimental_current.txt
+++ b/sqlite/sqlite/api/public_plus_experimental_current.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/api/restricted_2.1.0-alpha01.txt b/sqlite/sqlite/api/restricted_2.1.0-alpha01.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/restricted_2.1.0-alpha01.txt
+++ b/sqlite/sqlite/api/restricted_2.1.0-alpha01.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index f4688a2f..8e1ecd7 100644
--- a/sqlite/sqlite/api/restricted_current.txt
+++ b/sqlite/sqlite/api/restricted_current.txt
@@ -75,12 +75,14 @@
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final String? name;
+    field public final boolean useNoBackupDirectory;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
     method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
   }
 
   public static interface SupportSQLiteOpenHelper.Factory {
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
index 8c0b7f6..afe705f 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
@@ -20,6 +20,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
@@ -317,11 +318,27 @@
          */
         @NonNull
         public final SupportSQLiteOpenHelper.Callback callback;
+        /**
+         * If {@code true} the database will be stored in the no-backup directory.
+         */
+        public final boolean useNoBackupDirectory;
 
-        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
+        Configuration(
+                @NonNull Context context,
+                @Nullable String name,
+                @NonNull Callback callback) {
+            this(context, name, callback, false);
+        }
+
+        Configuration(
+                @NonNull Context context,
+                @Nullable String name,
+                @NonNull Callback callback,
+                boolean useNoBackupDirectory) {
             this.context = context;
             this.name = name;
             this.callback = callback;
+            this.useNoBackupDirectory = useNoBackupDirectory;
         }
 
         /**
@@ -341,7 +358,19 @@
             Context mContext;
             String mName;
             SupportSQLiteOpenHelper.Callback mCallback;
+            boolean mUseNoBackUpDirectory;
 
+            /**
+             * <p>
+             * Throws an {@link IllegalArgumentException} if the {@link Callback} is {@code null}.
+             * <p>
+             * Throws an {@link IllegalArgumentException} if the {@link Context} is {@code null}.
+             * <p>
+             * Throws an {@link IllegalArgumentException} if the {@link String} database
+             * name is {@code null}. {@see Context#getNoBackupFilesDir()}
+             *
+             * @return The {@link Configuration} instance
+             */
             @NonNull
             public Configuration build() {
                 if (mCallback == null) {
@@ -352,7 +381,12 @@
                     throw new IllegalArgumentException("Must set a non-null context to create"
                             + " the configuration.");
                 }
-                return new Configuration(mContext, mName, mCallback);
+                if (mUseNoBackUpDirectory && TextUtils.isEmpty(mName)) {
+                    throw new IllegalArgumentException(
+                            "Must set a non-null database name to a configuration that uses the "
+                                    + "no backup directory.");
+                }
+                return new Configuration(mContext, mName, mCallback, mUseNoBackUpDirectory);
             }
 
             Builder(@NonNull Context context) {
@@ -378,6 +412,18 @@
                 mCallback = callback;
                 return this;
             }
+
+            /**
+             * Sets whether to use a no backup directory or not.
+             * @param useNoBackUpDirectory If {@code true} the database file will be stored in the
+             *                             no-backup directory.
+             * @return this
+             */
+            @NonNull
+            public Builder noBackupDirectory(boolean useNoBackUpDirectory) {
+                mUseNoBackUpDirectory = useNoBackUpDirectory;
+                return this;
+            }
         }
     }