Support for lookaside configuration params

Modified SQLiteDatabase to allow passing lookaside configuration to its
static initializer. Encapsulated config in OpenParams and added an
overloaded version of openDatabase with the new parameter.

Configuration is changed in SQLiteConnection::nativeOpen, immediately
after opening the database since lookaside memory configuration can only
be changed when no connection is using it.

Added SQLiteOpenHelper.setLookasideConfig method that is called from
the constructor of the subclass.

Test: bit FrameworksCoreTests:android.database.DatabaseGeneralTest
Test: bit FrameworksCoreTests:android.database.SQLiteOpenHelperTest
Bug: 38499845
Change-Id: Ifb761229b43c89c090939030fc25b8c480b9b9e2
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fe849b8..7406b3f 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentValues;
@@ -34,10 +36,14 @@
 import android.util.Pair;
 import android.util.Printer;
 
+import com.android.internal.util.Preconditions;
+
 import dalvik.system.CloseGuard;
 
 import java.io.File;
 import java.io.FileFilter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -253,11 +259,14 @@
      */
     public static final int MAX_SQL_CACHE_SIZE = 100;
 
-    private SQLiteDatabase(String path, int openFlags, CursorFactory cursorFactory,
-            DatabaseErrorHandler errorHandler) {
+    private SQLiteDatabase(final String path, final int openFlags,
+            CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
+            int lookasideSlotSize, int lookasideSlotCount) {
         mCursorFactory = cursorFactory;
         mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
         mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
+        mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
+        mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
     }
 
     @Override
@@ -667,11 +676,30 @@
      * @return the newly opened database
      * @throws SQLiteException if the database cannot be opened
      */
-    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
+    public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
+            @DatabaseOpenFlags int flags) {
         return openDatabase(path, factory, flags, null);
     }
 
     /**
+     * Open the database according to the specified {@link OpenParams parameters}
+     *
+     * @param path to database file to open and/or create
+     * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}
+     * @return the newly opened database
+     * @throws SQLiteException if the database cannot be opened
+     */
+    public static SQLiteDatabase openDatabase(@NonNull String path,
+            @NonNull OpenParams openParams) {
+        Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
+        SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
+                openParams.mCursorFactory, openParams.mErrorHandler,
+                openParams.mLookasideSlotSize, openParams.mLookasideSlotCount);
+        db.open();
+        return db;
+    }
+
+    /**
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
@@ -690,9 +718,9 @@
      * @return the newly opened database
      * @throws SQLiteException if the database cannot be opened
      */
-    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
-            DatabaseErrorHandler errorHandler) {
-        SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
+    public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
+            @DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
+        SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1);
         db.open();
         return db;
     }
@@ -700,22 +728,24 @@
     /**
      * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
      */
-    public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
+    public static SQLiteDatabase openOrCreateDatabase(@NonNull File file,
+            @Nullable CursorFactory factory) {
         return openOrCreateDatabase(file.getPath(), factory);
     }
 
     /**
      * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
      */
-    public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
+    public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
+            @Nullable CursorFactory factory) {
         return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
     }
 
     /**
      * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler).
      */
-    public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
-            DatabaseErrorHandler errorHandler) {
+    public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
+            @Nullable CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) {
         return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
     }
 
@@ -726,7 +756,7 @@
      * @param file The database file path.
      * @return True if the database was successfully deleted.
      */
-    public static boolean deleteDatabase(File file) {
+    public static boolean deleteDatabase(@NonNull File file) {
         if (file == null) {
             throw new IllegalArgumentException("file must not be null");
         }
@@ -825,13 +855,29 @@
      *            cursor when query is called
      * @return a SQLiteDatabase object, or null if the database can't be created
      */
-    public static SQLiteDatabase create(CursorFactory factory) {
+    @NonNull
+    public static SQLiteDatabase create(@Nullable CursorFactory factory) {
         // This is a magic string with special meaning for SQLite.
         return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
                 factory, CREATE_IF_NECESSARY);
     }
 
     /**
+     * Create a memory backed SQLite database.  Its contents will be destroyed
+     * when the database is closed.
+     *
+     * <p>Sets the locale of the database to the  the system's current locale.
+     * Call {@link #setLocale} if you would like something else.</p>
+     * @param openParams configuration parameters that are used for opening SQLiteDatabase
+     * @return a SQLiteDatabase object, or null if the database can't be created
+     */
+    @NonNull
+    public static SQLiteDatabase createInMemory(@NonNull OpenParams openParams) {
+        return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
+                openParams.toBuilder().addOpenFlags(CREATE_IF_NECESSARY).build());
+    }
+
+    /**
      * Registers a CustomFunction callback as a function that can be called from
      * SQLite database triggers.
      *
@@ -2210,4 +2256,238 @@
     public interface CustomFunction {
         public void callback(String[] args);
     }
+
+    /**
+     * Wrapper for configuration parameters that are used for opening {@link SQLiteDatabase}
+     */
+    public static final class OpenParams {
+        private final int mOpenFlags;
+        private final CursorFactory mCursorFactory;
+        private final DatabaseErrorHandler mErrorHandler;
+        private final int mLookasideSlotSize;
+        private final int mLookasideSlotCount;
+
+        private OpenParams(int openFlags, CursorFactory cursorFactory,
+                DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount) {
+            mOpenFlags = openFlags;
+            mCursorFactory = cursorFactory;
+            mErrorHandler = errorHandler;
+            mLookasideSlotSize = lookasideSlotSize;
+            mLookasideSlotCount = lookasideSlotCount;
+        }
+
+        /**
+         * Returns size in bytes of each lookaside slot or -1 if not set.
+         *
+         * @see Builder#setLookasideConfig(int, int)
+         */
+        @IntRange(from = -1)
+        public int getLookasideSlotSize() {
+            return mLookasideSlotSize;
+        }
+
+        /**
+         * Returns total number of lookaside memory slots per database connection or -1 if not
+         * set.
+         *
+         * @see Builder#setLookasideConfig(int, int)
+         */
+        @IntRange(from = -1)
+        public int getLookasideSlotCount() {
+            return mLookasideSlotCount;
+        }
+
+        /**
+         * Returns flags to control database access mode
+         *
+         * @see Builder#setOpenFlags(int)
+         */
+        @DatabaseOpenFlags
+        public int getOpenFlags() {
+            return mOpenFlags;
+        }
+
+        /**
+         * Returns an optional factory class that is called to instantiate a cursor when query
+         * is called
+         *
+         * @see Builder#setCursorFactory(CursorFactory)
+         */
+        @Nullable
+        public CursorFactory getCursorFactory() {
+            return mCursorFactory;
+        }
+
+        /**
+         * Returns handler for database corruption errors
+         *
+         * @see Builder#setErrorHandler(DatabaseErrorHandler)
+         */
+        @Nullable
+        public DatabaseErrorHandler getErrorHandler() {
+            return mErrorHandler;
+        }
+
+        /**
+         * Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
+         * {@code this} parameters.
+         * @hide
+         */
+        @NonNull
+        public Builder toBuilder() {
+            return new Builder(this);
+        }
+
+        /**
+         * Builder for {@link OpenParams}.
+         */
+        public static final class Builder {
+            private int mLookasideSlotSize = -1;
+            private int mLookasideSlotCount = -1;
+            private int mOpenFlags;
+            private CursorFactory mCursorFactory;
+            private DatabaseErrorHandler mErrorHandler;
+
+            public Builder() {
+            }
+
+            public Builder(OpenParams params) {
+                mLookasideSlotSize = params.mLookasideSlotSize;
+                mLookasideSlotCount = params.mLookasideSlotCount;
+                mOpenFlags = params.mOpenFlags;
+                mCursorFactory = params.mCursorFactory;
+                mErrorHandler = params.mErrorHandler;
+            }
+
+            /**
+             * Configures
+             * <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
+             *
+             * <p>SQLite default settings will be used, if this method isn't called.
+             * Use {@code setLookasideConfig(0,0)} to disable lookaside
+             *
+             * @param slotSize The size in bytes of each lookaside slot.
+             * @param slotCount The total number of lookaside memory slots per database connection.
+             */
+            public Builder setLookasideConfig(@IntRange(from = 0) final int slotSize,
+                    @IntRange(from = 0) final int slotCount) {
+                Preconditions.checkArgument(slotSize >= 0,
+                        "lookasideSlotCount cannot be negative");
+                Preconditions.checkArgument(slotCount >= 0,
+                        "lookasideSlotSize cannot be negative");
+                Preconditions.checkArgument(
+                        (slotSize > 0 && slotCount > 0) || (slotCount == 0 && slotSize == 0),
+                        "Invalid configuration: " + slotSize + ", " + slotCount);
+
+                mLookasideSlotSize = slotSize;
+                mLookasideSlotCount = slotCount;
+                return this;
+            }
+
+            /**
+             * Returns true if {@link #ENABLE_WRITE_AHEAD_LOGGING} flag is set
+             * @hide
+             */
+            public boolean isWriteAheadLoggingEnabled() {
+                return (mOpenFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            }
+
+            /**
+             * Sets flags to control database access mode
+             * @param openFlags The new flags to set
+             * @see #OPEN_READWRITE
+             * @see #OPEN_READONLY
+             * @see #CREATE_IF_NECESSARY
+             * @see #NO_LOCALIZED_COLLATORS
+             * @see #ENABLE_WRITE_AHEAD_LOGGING
+             * @return same builder instance for chaining multiple calls into a single statement
+             */
+            @NonNull
+            public Builder setOpenFlags(@DatabaseOpenFlags int openFlags) {
+                mOpenFlags = openFlags;
+                return this;
+            }
+
+            /**
+             * Adds flags to control database access mode
+             *
+             * @param openFlags The new flags to add
+             * @return same builder instance for chaining multiple calls into a single statement
+             */
+            @NonNull
+            public Builder addOpenFlags(@DatabaseOpenFlags int openFlags) {
+                mOpenFlags |= openFlags;
+                return this;
+            }
+
+            /**
+             * Removes database access mode flags
+             *
+             * @param openFlags Flags to remove
+             * @return same builder instance for chaining multiple calls into a single statement
+             */
+            @NonNull
+            public Builder removeOpenFlags(@DatabaseOpenFlags int openFlags) {
+                mOpenFlags &= ~openFlags;
+                return this;
+            }
+
+            /**
+             * Sets {@link #ENABLE_WRITE_AHEAD_LOGGING} flag if {@code enabled} is {@code true},
+             * unsets otherwise
+             * @hide
+             */
+            public void setWriteAheadLoggingEnabled(boolean enabled) {
+                if (enabled) {
+                    addOpenFlags(ENABLE_WRITE_AHEAD_LOGGING);
+                } else {
+                    removeOpenFlags(ENABLE_WRITE_AHEAD_LOGGING);
+                }
+            }
+
+            /**
+             * Set an optional factory class that is called to instantiate a cursor when query
+             * is called.
+             *
+             * @param cursorFactory instance
+             * @return same builder instance for chaining multiple calls into a single statement
+             */
+            @NonNull
+            public Builder setCursorFactory(@Nullable CursorFactory cursorFactory) {
+                mCursorFactory = cursorFactory;
+                return this;
+            }
+
+
+            /**
+             * Sets {@link DatabaseErrorHandler} object to handle db corruption errors
+             */
+            @NonNull
+            public Builder setErrorHandler(@Nullable DatabaseErrorHandler errorHandler) {
+                mErrorHandler = errorHandler;
+                return this;
+            }
+
+            /**
+             * Creates an instance of {@link OpenParams} with the options that were previously set
+             * on this builder
+             */
+            @NonNull
+            public OpenParams build() {
+                return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
+                        mLookasideSlotCount);
+            }
+        }
+    }
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"OPEN_", "CREATE_", "NO_", "ENABLE_"}, value = {
+            OPEN_READWRITE,
+            OPEN_READONLY,
+            CREATE_IF_NECESSARY,
+            NO_LOCALIZED_COLLATORS,
+            ENABLE_WRITE_AHEAD_LOGGING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DatabaseOpenFlags {}
 }