diff --git a/app/build.gradle b/app/build.gradle
index 2ef95f5970..e46d496c8b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -40,11 +40,6 @@ android {
abortOnError false
}
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/README.md'
@@ -137,9 +132,6 @@ repositories {
}
configurations.all {
- // Workaround https://issuetracker.google.com/issues/138441698
- // Support @69c481c39a17d4e1e44a4eb298bb81c48f226eef
- exclude group: "androidx.room", module: "room-runtime"
// Workaround https://issuetracker.google.com/issues/134685570
exclude group: "androidx.lifecycle", module: "lifecycle-livedata"
}
@@ -204,9 +196,6 @@ dependencies {
// https://mvnrepository.com/artifact/androidx.room/room-runtime
implementation "androidx.room:room-runtime:$room_version"
- implementation "androidx.room:room-common:$room_version" // because of exclude
- // https://mvnrepository.com/artifact/androidx.sqlite/sqlite-framework
- implementation "androidx.sqlite:sqlite-framework:2.0.1" // because of exclude
annotationProcessor "androidx.room:room-compiler:$room_version"
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
diff --git a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl b/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl
deleted file mode 100644
index 7c702fff1b..0000000000
--- a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 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;
-
-/**
- * RPC Callbacks for {@link IMultiInstanceInvalidationService}.
- *
- * @hide
- */
-interface IMultiInstanceInvalidationCallback {
-
- /**
- * Called when invalidation is detected in another instance of the same database.
- *
- * @param tables List of invalidated table names
- */
- oneway void onInvalidation(in String[] tables);
-
-}
diff --git a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl b/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl
deleted file mode 100644
index 3b2c18c5ab..0000000000
--- a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2018 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.IMultiInstanceInvalidationCallback;
-
-/**
- * RPC Service that controls interaction about multi-instance invalidation.
- *
- * @hide
- */
-interface IMultiInstanceInvalidationService {
-
- /**
- * Registers a new {@link IMultiInstanceInvalidationCallback} as a client of this service.
- *
- * @param callback The RPC callback.
- * @param name The name of the database file as it is passed to {@link RoomDatabase.Builder}.
- * @return A new client ID. The client needs to hold on to this ID and pass it to the service
- * for subsequent calls.
- */
- int registerCallback(IMultiInstanceInvalidationCallback callback, String name);
-
- /**
- * Unregisters the specified {@link IMultiInstanceInvalidationCallback} from this service.
- *
- * Clients might die without explicitly calling this method. In that case, the service should
- * handle the clean up.
- *
- * @param callback The RPC callback.
- * @param clientId The client ID returned from {@link #registerCallback}.
- */
- void unregisterCallback(IMultiInstanceInvalidationCallback callback, int clientId);
-
- /**
- * Broadcasts invalidation of database tables to other clients registered to this service.
- *
- * The broadcast is delivered to {@link IMultiInstanceInvalidationCallback#onInvalidation} of
- * the registered clients. The client calling this method will not receive its own broadcast.
- * Clients that are associated with a different database file will not be notified.
- *
- * @param clientId The client ID returned from {@link #registerCallback}.
- * @param tables The names of invalidated tables.
- */
- oneway void broadcastInvalidation(int clientId, in String[] tables);
-
-}
diff --git a/app/src/main/java/androidx/room/DatabaseConfiguration.java b/app/src/main/java/androidx/room/DatabaseConfiguration.java
deleted file mode 100644
index f994445016..0000000000
--- a/app/src/main/java/androidx/room/DatabaseConfiguration.java
+++ /dev/null
@@ -1,294 +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 android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * Configuration class for a {@link RoomDatabase}.
- */
-@SuppressWarnings("WeakerAccess")
-public class DatabaseConfiguration {
-
- /**
- * The factory to use to access the database.
- */
- @NonNull
- public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
- /**
- * The context to use while connecting to the database.
- */
- @NonNull
- public final Context context;
- /**
- * The name of the database file or null if it is an in-memory database.
- */
- @Nullable
- public final String name;
-
- /**
- * Collection of available migrations.
- */
- @NonNull
- public final RoomDatabase.MigrationContainer migrationContainer;
-
- @Nullable
- public final List callbacks;
-
- /**
- * Whether Room should throw an exception for queries run on the main thread.
- */
- public final boolean allowMainThreadQueries;
-
- /**
- * The journal mode for this database.
- */
- public final RoomDatabase.JournalMode journalMode;
-
- /**
- * The Executor used to execute asynchronous queries.
- */
- @NonNull
- public final Executor queryExecutor;
-
- /**
- * The Executor used to execute asynchronous transactions.
- */
- @NonNull
- public final Executor transactionExecutor;
-
- /**
- * If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and
- * synchronized with other instances of the same {@link RoomDatabase} file, including those
- * in a separate process.
- */
- public final boolean multiInstanceInvalidation;
-
- /**
- * If true, Room should crash if a migration is missing.
- */
- public final boolean requireMigration;
-
- /**
- * If true, Room should perform a destructive migration when downgrading without an available
- * migration.
- */
- public final boolean allowDestructiveMigrationOnDowngrade;
-
- /**
- * The collection of schema versions from which migrations aren't required.
- */
- private final Set mMigrationNotRequiredFrom;
-
- /**
- * The assets path to a pre-packaged database to copy from.
- */
- @Nullable
- public final String copyFromAssetPath;
-
- /**
- * The pre-packaged database file to copy from.
- */
- @Nullable
- public final File copyFromFile;
-
-
- /**
- * Creates a database configuration with the given values.
- *
- * @deprecated Use {@link #DatabaseConfiguration(Context, String,
- * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
- * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)}
- *
- * @param context The application context.
- * @param name Name of the database, can be null if it is in memory.
- * @param sqliteOpenHelperFactory The open helper factory to use.
- * @param migrationContainer The migration container for migrations.
- * @param callbacks The list of callbacks for database events.
- * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
- * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
- * @param queryExecutor The Executor used to execute asynchronous queries.
- * @param requireMigration True if Room should require a valid migration if version changes,
- * instead of recreating the tables.
- * @param migrationNotRequiredFrom The collection of schema versions from which migrations
- * aren't required.
- *
- * @hide
- */
- @Deprecated
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
- @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
- @NonNull RoomDatabase.MigrationContainer migrationContainer,
- @Nullable List callbacks,
- boolean allowMainThreadQueries,
- RoomDatabase.JournalMode journalMode,
- @NonNull Executor queryExecutor,
- boolean requireMigration,
- @Nullable Set migrationNotRequiredFrom) {
- this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
- allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false,
- requireMigration, false, migrationNotRequiredFrom, null, null);
- }
-
- /**
- * Creates a database configuration with the given values.
- *
- * @deprecated Use {@link #DatabaseConfiguration(Context, String,
- * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
- * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)}
- *
- * @param context The application context.
- * @param name Name of the database, can be null if it is in memory.
- * @param sqliteOpenHelperFactory The open helper factory to use.
- * @param migrationContainer The migration container for migrations.
- * @param callbacks The list of callbacks for database events.
- * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
- * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
- * @param queryExecutor The Executor used to execute asynchronous queries.
- * @param transactionExecutor The Executor used to execute asynchronous transactions.
- * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
- * @param requireMigration True if Room should require a valid migration if version changes,
- * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
- * migration is supplied during a downgrade.
- * @param migrationNotRequiredFrom The collection of schema versions from which migrations
- * aren't required.
- *
- * @hide
- */
- @Deprecated
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
- @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
- @NonNull RoomDatabase.MigrationContainer migrationContainer,
- @Nullable List callbacks,
- boolean allowMainThreadQueries,
- RoomDatabase.JournalMode journalMode,
- @NonNull Executor queryExecutor,
- @NonNull Executor transactionExecutor,
- boolean multiInstanceInvalidation,
- boolean requireMigration,
- boolean allowDestructiveMigrationOnDowngrade,
- @Nullable Set migrationNotRequiredFrom) {
- this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
- allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
- multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
- migrationNotRequiredFrom, null, null);
- }
-
- /**
- * Creates a database configuration with the given values.
- *
- * @param context The application context.
- * @param name Name of the database, can be null if it is in memory.
- * @param sqliteOpenHelperFactory The open helper factory to use.
- * @param migrationContainer The migration container for migrations.
- * @param callbacks The list of callbacks for database events.
- * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
- * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
- * @param queryExecutor The Executor used to execute asynchronous queries.
- * @param transactionExecutor The Executor used to execute asynchronous transactions.
- * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
- * @param requireMigration True if Room should require a valid migration if version changes,
- * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
- * migration is supplied during a downgrade.
- * @param migrationNotRequiredFrom The collection of schema versions from which migrations
- * aren't required.
- * @param copyFromAssetPath The assets path to the pre-packaged database.
- * @param copyFromFile The pre-packaged database file.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
- @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
- @NonNull RoomDatabase.MigrationContainer migrationContainer,
- @Nullable List callbacks,
- boolean allowMainThreadQueries,
- RoomDatabase.JournalMode journalMode,
- @NonNull Executor queryExecutor,
- @NonNull Executor transactionExecutor,
- boolean multiInstanceInvalidation,
- boolean requireMigration,
- boolean allowDestructiveMigrationOnDowngrade,
- @Nullable Set migrationNotRequiredFrom,
- @Nullable String copyFromAssetPath,
- @Nullable File copyFromFile) {
- this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
- this.context = context;
- this.name = name;
- this.migrationContainer = migrationContainer;
- this.callbacks = callbacks;
- this.allowMainThreadQueries = allowMainThreadQueries;
- this.journalMode = journalMode;
- this.queryExecutor = queryExecutor;
- this.transactionExecutor = transactionExecutor;
- this.multiInstanceInvalidation = multiInstanceInvalidation;
- this.requireMigration = requireMigration;
- this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade;
- this.mMigrationNotRequiredFrom = migrationNotRequiredFrom;
- this.copyFromAssetPath = copyFromAssetPath;
- this.copyFromFile = copyFromFile;
- }
-
- /**
- * Returns whether a migration is required from the specified version.
- *
- * @param version The schema version.
- * @return True if a valid migration is required, false otherwise.
- *
- * @deprecated Use {@link #isMigrationRequired(int, int)} which takes
- * {@link #allowDestructiveMigrationOnDowngrade} into account.
- */
- @Deprecated
- public boolean isMigrationRequiredFrom(int version) {
- return isMigrationRequired(version, version + 1);
- }
-
- /**
- * Returns whether a migration is required between two versions.
- *
- * @param fromVersion The old schema version.
- * @param toVersion The new schema version.
- * @return True if a valid migration is required, false otherwise.
- */
- public boolean isMigrationRequired(int fromVersion, int toVersion) {
- // Migrations are not required if its a downgrade AND destructive migration during downgrade
- // has been allowed.
- final boolean isDowngrade = fromVersion > toVersion;
- if (isDowngrade && allowDestructiveMigrationOnDowngrade) {
- return false;
- }
-
- // Migrations are required between the two versions if we generally require migrations
- // AND EITHER there are no exceptions OR the supplied fromVersion is not one of the
- // exceptions.
- return requireMigration
- && (mMigrationNotRequiredFrom == null
- || !mMigrationNotRequiredFrom.contains(fromVersion));
- }
-}
diff --git a/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java b/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java
deleted file mode 100644
index 154103cfcb..0000000000
--- a/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java
+++ /dev/null
@@ -1,115 +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 androidx.annotation.RestrictTo;
-import androidx.sqlite.db.SupportSQLiteStatement;
-
-/**
- * Implementations of this class knows how to delete or update a particular entity.
- *
- * This is an internal library class and all of its implementations are auto-generated.
- *
- * @param The type parameter of the entity to be deleted
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-@SuppressWarnings({"WeakerAccess", "unused"})
-public abstract class EntityDeletionOrUpdateAdapter extends SharedSQLiteStatement {
- /**
- * Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given
- * database.
- *
- * @param database The database to delete / update the item in.
- */
- public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
- super(database);
- }
-
- /**
- * Create the deletion or update query
- *
- * @return An SQL query that can delete or update instances of T.
- */
- @Override
- protected abstract String createQuery();
-
- /**
- * Binds the entity into the given statement.
- *
- * @param statement The SQLite statement that prepared for the query returned from
- * createQuery.
- * @param entity The entity of type T.
- */
- protected abstract void bind(SupportSQLiteStatement statement, T entity);
-
- /**
- * Deletes or updates the given entities in the database and returns the affected row count.
- *
- * @param entity The entity to delete or update
- * @return The number of affected rows
- */
- public final int handle(T entity) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- bind(stmt, entity);
- return stmt.executeUpdateDelete();
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Deletes or updates the given entities in the database and returns the affected row count.
- *
- * @param entities Entities to delete or update
- * @return The number of affected rows
- */
- public final int handleMultiple(Iterable extends T> entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- int total = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- total += stmt.executeUpdateDelete();
- }
- return total;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Deletes or updates the given entities in the database and returns the affected row count.
- *
- * @param entities Entities to delete or update
- * @return The number of affected rows
- */
- public final int handleMultiple(T[] entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- int total = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- total += stmt.executeUpdateDelete();
- }
- return total;
- } finally {
- release(stmt);
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/EntityInsertionAdapter.java b/app/src/main/java/androidx/room/EntityInsertionAdapter.java
deleted file mode 100644
index 3046d6cd0b..0000000000
--- a/app/src/main/java/androidx/room/EntityInsertionAdapter.java
+++ /dev/null
@@ -1,251 +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 androidx.annotation.RestrictTo;
-import androidx.sqlite.db.SupportSQLiteStatement;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Implementations of this class knows how to insert a particular entity.
- *
- * This is an internal library class and all of its implementations are auto-generated.
- *
- * @param The type parameter of the entity to be inserted
- * @hide
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class EntityInsertionAdapter extends SharedSQLiteStatement {
- /**
- * Creates an InsertionAdapter that can insert the entity type T into the given database.
- *
- * @param database The database to insert into.
- */
- public EntityInsertionAdapter(RoomDatabase database) {
- super(database);
- }
-
- /**
- * Binds the entity into the given statement.
- *
- * @param statement The SQLite statement that prepared for the query returned from
- * createInsertQuery.
- * @param entity The entity of type T.
- */
- protected abstract void bind(SupportSQLiteStatement statement, T entity);
-
- /**
- * Inserts the entity into the database.
- *
- * @param entity The entity to insert
- */
- public final void insert(T entity) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- bind(stmt, entity);
- stmt.executeInsert();
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database.
- *
- * @param entities Entities to insert
- */
- public final void insert(T[] entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- for (T entity : entities) {
- bind(stmt, entity);
- stmt.executeInsert();
- }
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database.
- *
- * @param entities Entities to insert
- */
- public final void insert(Iterable extends T> entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- for (T entity : entities) {
- bind(stmt, entity);
- stmt.executeInsert();
- }
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entity into the database and returns the row id.
- *
- * @param entity The entity to insert
- * @return The SQLite row id or -1 if no row is inserted
- */
- public final long insertAndReturnId(T entity) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- bind(stmt, entity);
- return stmt.executeInsert();
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final long[] insertAndReturnIdsArray(Collection extends T> entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final long[] result = new long[entities.size()];
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result[index] = stmt.executeInsert();
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final long[] insertAndReturnIdsArray(T[] entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final long[] result = new long[entities.length];
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result[index] = stmt.executeInsert();
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final Long[] insertAndReturnIdsArrayBox(Collection extends T> entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final Long[] result = new Long[entities.size()];
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result[index] = stmt.executeInsert();
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final Long[] insertAndReturnIdsArrayBox(T[] entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final Long[] result = new Long[entities.length];
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result[index] = stmt.executeInsert();
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final List insertAndReturnIdsList(T[] entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final List result = new ArrayList<>(entities.length);
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result.add(index, stmt.executeInsert());
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-
- /**
- * Inserts the given entities into the database and returns the row ids.
- *
- * @param entities Entities to insert
- * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
- */
- public final List insertAndReturnIdsList(Collection extends T> entities) {
- final SupportSQLiteStatement stmt = acquire();
- try {
- final List result = new ArrayList<>(entities.size());
- int index = 0;
- for (T entity : entities) {
- bind(stmt, entity);
- result.add(index, stmt.executeInsert());
- index++;
- }
- return result;
- } finally {
- release(stmt);
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java b/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java
deleted file mode 100644
index e1e8155f8a..0000000000
--- a/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2018 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.VisibleForTesting;
-import androidx.lifecycle.LiveData;
-
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-/**
- * A helper class that maintains {@link RoomTrackingLiveData} instances for an
- * {@link InvalidationTracker}.
- *
- * We keep a strong reference to active LiveData instances to avoid garbage collection in case
- * developer does not hold onto the returned LiveData.
- */
-class InvalidationLiveDataContainer {
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final Set mLiveDataSet = Collections.newSetFromMap(
- new IdentityHashMap()
- );
- private final RoomDatabase mDatabase;
-
- InvalidationLiveDataContainer(RoomDatabase database) {
- mDatabase = database;
- }
-
- LiveData create(String[] tableNames, boolean inTransaction,
- Callable computeFunction) {
- return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
- tableNames);
- }
-
- void onActive(LiveData liveData) {
- mLiveDataSet.add(liveData);
- }
-
- void onInactive(LiveData liveData) {
- mLiveDataSet.remove(liveData);
- }
-}
diff --git a/app/src/main/java/androidx/room/InvalidationTracker.java b/app/src/main/java/androidx/room/InvalidationTracker.java
deleted file mode 100644
index f35b7f9550..0000000000
--- a/app/src/main/java/androidx/room/InvalidationTracker.java
+++ /dev/null
@@ -1,853 +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 android.annotation.SuppressLint;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.internal.SafeIterableMap;
-import androidx.lifecycle.LiveData;
-import androidx.sqlite.db.SimpleSQLiteQuery;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteStatement;
-
-import java.lang.ref.WeakReference;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
-
-/**
- * InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about
- * these tables.
- */
-// Some details on how the InvalidationTracker works:
-// * An in memory table is created with (table_id, invalidated) table_id is a hardcoded int from
-// initialization, while invalidated is a boolean bit to indicate if the table has been invalidated.
-// * ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for).
-// * Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states.
-// * After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated
-// tables.
-// * Each update (write operation) on one of the observed tables triggers an update into the
-// memory table table, flipping the invalidated flag ON.
-// * When multi-instance invalidation is turned on, MultiInstanceInvalidationClient will be created.
-// It works as an Observer, and notifies other instances of table invalidation.
-public class InvalidationTracker {
-
- private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
-
- private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
-
- private static final String TABLE_ID_COLUMN_NAME = "table_id";
-
- private static final String INVALIDATED_COLUMN_NAME = "invalidated";
-
- private static final String CREATE_TRACKING_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
- + "(" + TABLE_ID_COLUMN_NAME + " INTEGER PRIMARY KEY, "
- + INVALIDATED_COLUMN_NAME + " INTEGER NOT NULL DEFAULT 0)";
-
- @VisibleForTesting
- static final String RESET_UPDATED_TABLES_SQL = "UPDATE " + UPDATE_TABLE_NAME
- + " SET " + INVALIDATED_COLUMN_NAME + " = 0 WHERE " + INVALIDATED_COLUMN_NAME + " = 1 ";
-
- @VisibleForTesting
- static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
- + " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;";
-
- @NonNull
- @VisibleForTesting
- final HashMap mTableIdLookup;
- final String[] mTableNames;
-
- @NonNull
- private Map> mViewTables;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final RoomDatabase mDatabase;
-
- AtomicBoolean mPendingRefresh = new AtomicBoolean(false);
-
- private volatile boolean mInitialized = false;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- volatile SupportSQLiteStatement mCleanupStatement;
-
- private ObservedTableTracker mObservedTableTracker;
-
- private final InvalidationLiveDataContainer mInvalidationLiveDataContainer;
-
- // should be accessed with synchronization only.
- @VisibleForTesting
- @SuppressLint("RestrictedApi")
- final SafeIterableMap mObserverMap = new SafeIterableMap<>();
-
- private MultiInstanceInvalidationClient mMultiInstanceInvalidationClient;
-
- /**
- * Used by the generated code.
- *
- * @hide
- */
- @SuppressWarnings("WeakerAccess")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public InvalidationTracker(RoomDatabase database, String... tableNames) {
- this(database, new HashMap(), Collections.>emptyMap(),
- tableNames);
- }
-
- /**
- * Used by the generated code.
- *
- * @hide
- */
- @SuppressWarnings("WeakerAccess")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public InvalidationTracker(RoomDatabase database, Map shadowTablesMap,
- Map> viewTables, String... tableNames) {
- mDatabase = database;
- mObservedTableTracker = new ObservedTableTracker(tableNames.length);
- mTableIdLookup = new HashMap<>();
- mViewTables = viewTables;
- mInvalidationLiveDataContainer = new InvalidationLiveDataContainer(mDatabase);
- final int size = tableNames.length;
- mTableNames = new String[size];
- for (int id = 0; id < size; id++) {
- final String tableName = tableNames[id].toLowerCase(Locale.US);
- mTableIdLookup.put(tableName, id);
- String shadowTableName = shadowTablesMap.get(tableNames[id]);
- if (shadowTableName != null) {
- mTableNames[id] = shadowTableName.toLowerCase(Locale.US);
- } else {
- mTableNames[id] = tableName;
- }
- }
- // Adjust table id lookup for those tables whose shadow table is another already mapped
- // table (e.g. external content fts tables).
- for (Map.Entry shadowTableEntry : shadowTablesMap.entrySet()) {
- String shadowTableName = shadowTableEntry.getValue().toLowerCase(Locale.US);
- if (mTableIdLookup.containsKey(shadowTableName)) {
- String tableName = shadowTableEntry.getKey().toLowerCase(Locale.US);
- mTableIdLookup.put(tableName, mTableIdLookup.get(shadowTableName));
- }
- }
- }
-
- /**
- * Internal method to initialize table tracking.
- *
- * You should never call this method, it is called by the generated code.
- */
- void internalInit(SupportSQLiteDatabase database) {
- synchronized (this) {
- if (mInitialized) {
- Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");
- return;
- }
-
- // These actions are not in a transaction because temp_store is not allowed to be
- // performed on a transaction, and recursive_triggers is not affected by transactions.
- database.execSQL("PRAGMA temp_store = MEMORY;");
- database.execSQL("PRAGMA recursive_triggers='ON';");
- database.execSQL(CREATE_TRACKING_TABLE_SQL);
- syncTriggers(database);
- mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL);
- mInitialized = true;
- }
- }
-
- void startMultiInstanceInvalidation(Context context, String name) {
- mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name, this,
- mDatabase.getQueryExecutor());
- }
-
- void stopMultiInstanceInvalidation() {
- if (mMultiInstanceInvalidationClient != null) {
- mMultiInstanceInvalidationClient.stop();
- mMultiInstanceInvalidationClient = null;
- }
- }
-
- private static void appendTriggerName(StringBuilder builder, String tableName,
- String triggerType) {
- builder.append("`")
- .append("room_table_modification_trigger_")
- .append(tableName)
- .append("_")
- .append(triggerType)
- .append("`");
- }
-
- private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
- final String tableName = mTableNames[tableId];
- StringBuilder stringBuilder = new StringBuilder();
- for (String trigger : TRIGGERS) {
- stringBuilder.setLength(0);
- stringBuilder.append("DROP TRIGGER IF EXISTS ");
- appendTriggerName(stringBuilder, tableName, trigger);
- writableDb.execSQL(stringBuilder.toString());
- }
- }
-
- private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
- writableDb.execSQL(
- "INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)");
- final String tableName = mTableNames[tableId];
- StringBuilder stringBuilder = new StringBuilder();
- for (String trigger : TRIGGERS) {
- stringBuilder.setLength(0);
- stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
- appendTriggerName(stringBuilder, tableName, trigger);
- stringBuilder.append(" AFTER ")
- .append(trigger)
- .append(" ON `")
- .append(tableName)
- .append("` BEGIN UPDATE ")
- .append(UPDATE_TABLE_NAME)
- .append(" SET ").append(INVALIDATED_COLUMN_NAME).append(" = 1")
- .append(" WHERE ").append(TABLE_ID_COLUMN_NAME).append(" = ").append(tableId)
- .append(" AND ").append(INVALIDATED_COLUMN_NAME).append(" = 0")
- .append("; END");
- writableDb.execSQL(stringBuilder.toString());
- }
- }
-
- /**
- * Adds the given observer to the observers list and it will be notified if any table it
- * observes changes.
- *
- * Database changes are pulled on another thread so in some race conditions, the observer might
- * be invoked for changes that were done before it is added.
- *
- * If the observer already exists, this is a no-op call.
- *
- * If one of the tables in the Observer does not exist in the database, this method throws an
- * {@link IllegalArgumentException}.
- *
- * @param observer The observer which listens the database for changes.
- */
- @SuppressLint("RestrictedApi")
- @WorkerThread
- public void addObserver(@NonNull Observer observer) {
- final String[] tableNames = resolveViews(observer.mTables);
- int[] tableIds = new int[tableNames.length];
- final int size = tableNames.length;
-
- for (int i = 0; i < size; i++) {
- Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));
- if (tableId == null) {
- throw new IllegalArgumentException("There is no table with name " + tableNames[i]);
- }
- tableIds[i] = tableId;
- }
- ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames);
- ObserverWrapper currentObserver;
- synchronized (mObserverMap) {
- currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
- }
- if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
- syncTriggers();
- }
- }
-
- private String[] validateAndResolveTableNames(String[] tableNames) {
- String[] resolved = resolveViews(tableNames);
- for (String tableName : resolved) {
- if (!mTableIdLookup.containsKey(tableName.toLowerCase(Locale.US))) {
- throw new IllegalArgumentException("There is no table with name " + tableName);
- }
- }
- return resolved;
- }
-
- /**
- * Resolves the list of tables and views into a list of unique tables that are underlying them.
- *
- * @param names The names of tables or views.
- * @return The names of the underlying tables.
- */
- private String[] resolveViews(String[] names) {
- Set tables = new HashSet<>();
- for (String name : names) {
- final String lowercase = name.toLowerCase(Locale.US);
- if (mViewTables.containsKey(lowercase)) {
- tables.addAll(mViewTables.get(lowercase));
- } else {
- tables.add(name);
- }
- }
- return tables.toArray(new String[tables.size()]);
- }
-
- /**
- * Adds an observer but keeps a weak reference back to it.
- *
- * Note that you cannot remove this observer once added. It will be automatically removed
- * when the observer is GC'ed.
- *
- * @param observer The observer to which InvalidationTracker will keep a weak reference.
- * @hide
- */
- @SuppressWarnings("unused")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public void addWeakObserver(Observer observer) {
- addObserver(new WeakObserver(this, observer));
- }
-
- /**
- * Removes the observer from the observers list.
- *
- * @param observer The observer to remove.
- */
- @SuppressLint("RestrictedApi")
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- public void removeObserver(@NonNull final Observer observer) {
- ObserverWrapper wrapper;
- synchronized (mObserverMap) {
- wrapper = mObserverMap.remove(observer);
- }
- if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
- syncTriggers();
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean ensureInitialization() {
- if (!mDatabase.isOpen()) {
- return false;
- }
- if (!mInitialized) {
- // trigger initialization
- mDatabase.getOpenHelper().getWritableDatabase();
- }
- if (!mInitialized) {
- Log.e(Room.LOG_TAG, "database is not initialized even though it is open");
- return false;
- }
- return true;
- }
-
- @VisibleForTesting
- Runnable mRefreshRunnable = new Runnable() {
- @Override
- public void run() {
- final Lock closeLock = mDatabase.getCloseLock();
- Set invalidatedTableIds = null;
- try {
- closeLock.lock();
-
- if (!ensureInitialization()) {
- return;
- }
-
- if (!mPendingRefresh.compareAndSet(true, false)) {
- // no pending refresh
- return;
- }
-
- if (mDatabase.inTransaction()) {
- // current thread is in a transaction. when it ends, it will invoke
- // refreshRunnable again. mPendingRefresh is left as false on purpose
- // so that the last transaction can flip it on again.
- return;
- }
-
- if (mDatabase.mWriteAheadLoggingEnabled) {
- // This transaction has to be on the underlying DB rather than the RoomDatabase
- // in order to avoid a recursive loop after endTransaction.
- SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
- db.beginTransaction();
- try {
- invalidatedTableIds = checkUpdatedTable();
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- } else {
- invalidatedTableIds = checkUpdatedTable();
- }
- } catch (IllegalStateException | SQLiteException exception) {
- // may happen if db is closed. just log.
- Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
- exception);
- } finally {
- closeLock.unlock();
- }
- if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
- synchronized (mObserverMap) {
- for (Map.Entry entry : mObserverMap) {
- entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);
- }
- }
- }
- }
-
- private Set checkUpdatedTable() {
- HashSet invalidatedTableIds = new HashSet<>();
- Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));
- //noinspection TryFinallyCanBeTryWithResources
- try {
- while (cursor.moveToNext()) {
- final int tableId = cursor.getInt(0);
- invalidatedTableIds.add(tableId);
- }
- } finally {
- cursor.close();
- }
- if (!invalidatedTableIds.isEmpty()) {
- mCleanupStatement.executeUpdateDelete();
- }
- return invalidatedTableIds;
- }
- };
-
- /**
- * Enqueues a task to refresh the list of updated tables.
- *
- * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
- * if you have another connection to the database or directly use {@link
- * SupportSQLiteDatabase}, you may need to call this manually.
- */
- @SuppressWarnings("WeakerAccess")
- public void refreshVersionsAsync() {
- // TODO we should consider doing this sync instead of async.
- if (mPendingRefresh.compareAndSet(false, true)) {
- mDatabase.getQueryExecutor().execute(mRefreshRunnable);
- }
- }
-
- /**
- * Check versions for tables, and run observers synchronously if tables have been updated.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- @WorkerThread
- public void refreshVersionsSync() {
- syncTriggers();
- mRefreshRunnable.run();
- }
-
- /**
- * Notifies all the registered {@link Observer}s of table changes.
- *
- * This can be used for notifying invalidation that cannot be detected by this
- * {@link InvalidationTracker}, for example, invalidation from another process.
- *
- * @param tables The invalidated tables.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public void notifyObserversByTableNames(String... tables) {
- synchronized (mObserverMap) {
- for (Map.Entry entry : mObserverMap) {
- if (!entry.getKey().isRemote()) {
- entry.getValue().notifyByTableNames(tables);
- }
- }
- }
- }
-
- void syncTriggers(SupportSQLiteDatabase database) {
- if (database.inTransaction()) {
- // we won't run this inside another transaction.
- return;
- }
- try {
- // This method runs in a while loop because while changes are synced to db, another
- // runnable may be skipped. If we cause it to skip, we need to do its work.
- while (true) {
- Lock closeLock = mDatabase.getCloseLock();
- closeLock.lock();
- try {
- // there is a potential race condition where another mSyncTriggers runnable
- // can start running right after we get the tables list to sync.
- final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
- if (tablesToSync == null) {
- return;
- }
- final int limit = tablesToSync.length;
- database.beginTransaction();
- try {
- for (int tableId = 0; tableId < limit; tableId++) {
- switch (tablesToSync[tableId]) {
- case ObservedTableTracker.ADD:
- startTrackingTable(database, tableId);
- break;
- case ObservedTableTracker.REMOVE:
- stopTrackingTable(database, tableId);
- break;
- }
- }
- database.setTransactionSuccessful();
- } finally {
- database.endTransaction();
- }
- mObservedTableTracker.onSyncCompleted();
- } finally {
- closeLock.unlock();
- }
- }
- } catch (IllegalStateException | SQLiteException exception) {
- // may happen if db is closed. just log.
- Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
- exception);
- }
- }
-
- /**
- * Called by RoomDatabase before each beginTransaction call.
- *
- * It is important that pending trigger changes are applied to the database before any query
- * runs. Otherwise, we may miss some changes.
- *
- * This api should eventually be public.
- */
- void syncTriggers() {
- if (!mDatabase.isOpen()) {
- return;
- }
- syncTriggers(mDatabase.getOpenHelper().getWritableDatabase());
- }
-
- /**
- * Creates a LiveData that computes the given function once and for every other invalidation
- * of the database.
- *
- * Holds a strong reference to the created LiveData as long as it is active.
- *
- * @deprecated Use {@link #createLiveData(String[], boolean, Callable)}
- *
- * @param computeFunction The function that calculates the value
- * @param tableNames The list of tables to observe
- * @param The return type
- * @return A new LiveData that computes the given function when the given list of tables
- * invalidates.
- * @hide
- */
- @Deprecated
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public LiveData createLiveData(String[] tableNames, Callable computeFunction) {
- return createLiveData(tableNames, false, computeFunction);
- }
-
- /**
- * Creates a LiveData that computes the given function once and for every other invalidation
- * of the database.
- *
- * Holds a strong reference to the created LiveData as long as it is active.
- *
- * @param tableNames The list of tables to observe
- * @param inTransaction True if the computeFunction will be done in a transaction, false
- * otherwise.
- * @param computeFunction The function that calculates the value
- * @param The return type
- * @return A new LiveData that computes the given function when the given list of tables
- * invalidates.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public LiveData createLiveData(String[] tableNames, boolean inTransaction,
- Callable computeFunction) {
- return mInvalidationLiveDataContainer.create(
- validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
- }
-
- /**
- * Wraps an observer and keeps the table information.
- *
- * Internally table ids are used which may change from database to database so the table
- * related information is kept here rather than in the Observer.
- */
- @SuppressWarnings("WeakerAccess")
- static class ObserverWrapper {
- final int[] mTableIds;
- private final String[] mTableNames;
- final Observer mObserver;
- private final Set mSingleTableSet;
-
- ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames) {
- mObserver = observer;
- mTableIds = tableIds;
- mTableNames = tableNames;
- if (tableIds.length == 1) {
- HashSet set = new HashSet<>();
- set.add(mTableNames[0]);
- mSingleTableSet = Collections.unmodifiableSet(set);
- } else {
- mSingleTableSet = null;
- }
- }
-
- /**
- * Notifies the underlying {@link #mObserver} if any of the observed tables are invalidated
- * based on the given invalid status set.
- *
- * @param invalidatedTablesIds The table ids of the tables that are invalidated.
- */
- void notifyByTableInvalidStatus(Set invalidatedTablesIds) {
- Set invalidatedTables = null;
- final int size = mTableIds.length;
- for (int index = 0; index < size; index++) {
- final int tableId = mTableIds[index];
- if (invalidatedTablesIds.contains(tableId)) {
- if (size == 1) {
- // Optimization for a single-table observer
- invalidatedTables = mSingleTableSet;
- } else {
- if (invalidatedTables == null) {
- invalidatedTables = new HashSet<>(size);
- }
- invalidatedTables.add(mTableNames[index]);
- }
- }
- }
- if (invalidatedTables != null) {
- mObserver.onInvalidated(invalidatedTables);
- }
- }
-
- /**
- * Notifies the underlying {@link #mObserver} if it observes any of the specified
- * {@code tables}.
- *
- * @param tables The invalidated table names.
- */
- void notifyByTableNames(String[] tables) {
- Set invalidatedTables = null;
- if (mTableNames.length == 1) {
- for (String table : tables) {
- if (table.equalsIgnoreCase(mTableNames[0])) {
- // Optimization for a single-table observer
- invalidatedTables = mSingleTableSet;
- break;
- }
- }
- } else {
- HashSet set = new HashSet<>();
- for (String table : tables) {
- for (String ourTable : mTableNames) {
- if (ourTable.equalsIgnoreCase(table)) {
- set.add(ourTable);
- break;
- }
- }
- }
- if (set.size() > 0) {
- invalidatedTables = set;
- }
- }
- if (invalidatedTables != null) {
- mObserver.onInvalidated(invalidatedTables);
- }
- }
- }
-
- /**
- * An observer that can listen for changes in the database.
- */
- public abstract static class Observer {
- final String[] mTables;
-
- /**
- * Observes the given list of tables and views.
- *
- * @param firstTable The name of the table or view.
- * @param rest More names of tables or views.
- */
- @SuppressWarnings("unused")
- protected Observer(@NonNull String firstTable, String... rest) {
- mTables = Arrays.copyOf(rest, rest.length + 1);
- mTables[rest.length] = firstTable;
- }
-
- /**
- * Observes the given list of tables and views.
- *
- * @param tables The list of tables or views to observe for changes.
- */
- public Observer(@NonNull String[] tables) {
- // copy tables in case user modifies them afterwards
- mTables = Arrays.copyOf(tables, tables.length);
- }
-
- /**
- * Called when one of the observed tables is invalidated in the database.
- *
- * @param tables A set of invalidated tables. This is useful when the observer targets
- * multiple tables and you want to know which table is invalidated. This will
- * be names of underlying tables when you are observing views.
- */
- public abstract void onInvalidated(@NonNull Set tables);
-
- boolean isRemote() {
- return false;
- }
- }
-
- /**
- * Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/
- * triggers in the database.
- *
- * This class is thread safe
- */
- static class ObservedTableTracker {
- static final int NO_OP = 0; // don't change trigger state for this table
- static final int ADD = 1; // add triggers for this table
- static final int REMOVE = 2; // remove triggers for this table
-
- // number of observers per table
- final long[] mTableObservers;
- // trigger state for each table at last sync
- // this field is updated when syncAndGet is called.
- final boolean[] mTriggerStates;
- // when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP
- final int[] mTriggerStateChanges;
-
- boolean mNeedsSync;
-
- /**
- * After we return non-null value from getTablesToSync, we expect a onSyncCompleted before
- * returning any non-null value from getTablesToSync.
- * This allows us to workaround any multi-threaded state syncing issues.
- */
- boolean mPendingSync;
-
- ObservedTableTracker(int tableCount) {
- mTableObservers = new long[tableCount];
- mTriggerStates = new boolean[tableCount];
- mTriggerStateChanges = new int[tableCount];
- Arrays.fill(mTableObservers, 0);
- Arrays.fill(mTriggerStates, false);
- }
-
- /**
- * @return true if # of triggers is affected.
- */
- boolean onAdded(int... tableIds) {
- boolean needTriggerSync = false;
- synchronized (this) {
- for (int tableId : tableIds) {
- final long prevObserverCount = mTableObservers[tableId];
- mTableObservers[tableId] = prevObserverCount + 1;
- if (prevObserverCount == 0) {
- mNeedsSync = true;
- needTriggerSync = true;
- }
- }
- }
- return needTriggerSync;
- }
-
- /**
- * @return true if # of triggers is affected.
- */
- boolean onRemoved(int... tableIds) {
- boolean needTriggerSync = false;
- synchronized (this) {
- for (int tableId : tableIds) {
- final long prevObserverCount = mTableObservers[tableId];
- mTableObservers[tableId] = prevObserverCount - 1;
- if (prevObserverCount == 1) {
- mNeedsSync = true;
- needTriggerSync = true;
- }
- }
- }
- return needTriggerSync;
- }
-
- /**
- * If this returns non-null, you must call onSyncCompleted.
- *
- * @return int[] An int array where the index for each tableId has the action for that
- * table.
- */
- @Nullable
- int[] getTablesToSync() {
- synchronized (this) {
- if (!mNeedsSync || mPendingSync) {
- return null;
- }
- final int tableCount = mTableObservers.length;
- for (int i = 0; i < tableCount; i++) {
- final boolean newState = mTableObservers[i] > 0;
- if (newState != mTriggerStates[i]) {
- mTriggerStateChanges[i] = newState ? ADD : REMOVE;
- } else {
- mTriggerStateChanges[i] = NO_OP;
- }
- mTriggerStates[i] = newState;
- }
- mPendingSync = true;
- mNeedsSync = false;
- return mTriggerStateChanges;
- }
- }
-
- /**
- * if getTablesToSync returned non-null, the called should call onSyncCompleted once it
- * is done.
- */
- void onSyncCompleted() {
- synchronized (this) {
- mPendingSync = false;
- }
- }
- }
-
- /**
- * An Observer wrapper that keeps a weak reference to the given object.
- *
- * This class will automatically unsubscribe when the wrapped observer goes out of memory.
- */
- static class WeakObserver extends Observer {
- final InvalidationTracker mTracker;
- final WeakReference mDelegateRef;
-
- WeakObserver(InvalidationTracker tracker, Observer delegate) {
- super(delegate.mTables);
- mTracker = tracker;
- mDelegateRef = new WeakReference<>(delegate);
- }
-
- @Override
- public void onInvalidated(@NonNull Set tables) {
- final Observer observer = mDelegateRef.get();
- if (observer == null) {
- mTracker.removeObserver(this);
- } else {
- observer.onInvalidated(tables);
- }
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java b/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java
deleted file mode 100644
index 3aeddcc66a..0000000000
--- a/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2018 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.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Handles all the communication from {@link RoomDatabase} and {@link InvalidationTracker} to
- * {@link MultiInstanceInvalidationService}.
- */
-class MultiInstanceInvalidationClient {
-
- /**
- * The application context.
- */
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final Context mAppContext;
-
- /**
- * The name of the database file.
- */
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final String mName;
-
- /**
- * The client ID assigned by {@link MultiInstanceInvalidationService}.
- */
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- int mClientId;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final InvalidationTracker mInvalidationTracker;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final InvalidationTracker.Observer mObserver;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- @Nullable
- IMultiInstanceInvalidationService mService;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final Executor mExecutor;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final IMultiInstanceInvalidationCallback mCallback =
- new IMultiInstanceInvalidationCallback.Stub() {
- @Override
- public void onInvalidation(final String[] tables) {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mInvalidationTracker.notifyObserversByTableNames(tables);
- }
- });
- }
- };
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final AtomicBoolean mStopped = new AtomicBoolean(false);
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final ServiceConnection mServiceConnection = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IMultiInstanceInvalidationService.Stub.asInterface(service);
- mExecutor.execute(mSetUpRunnable);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mExecutor.execute(mRemoveObserverRunnable);
- mService = null;
- }
-
- };
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final Runnable mSetUpRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- final IMultiInstanceInvalidationService service = mService;
- if (service != null) {
- mClientId = service.registerCallback(mCallback, mName);
- mInvalidationTracker.addObserver(mObserver);
- }
- } catch (RemoteException e) {
- Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e);
- }
- }
- };
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final Runnable mRemoveObserverRunnable = new Runnable() {
- @Override
- public void run() {
- mInvalidationTracker.removeObserver(mObserver);
- }
- };
-
- private final Runnable mTearDownRunnable = new Runnable() {
- @Override
- public void run() {
- mInvalidationTracker.removeObserver(mObserver);
- try {
- final IMultiInstanceInvalidationService service = mService;
- if (service != null) {
- service.unregisterCallback(mCallback, mClientId);
- }
- } catch (RemoteException e) {
- Log.w(Room.LOG_TAG, "Cannot unregister multi-instance invalidation callback", e);
- }
- mAppContext.unbindService(mServiceConnection);
- }
- };
-
- /**
- * @param context The Context to be used for binding
- * {@link IMultiInstanceInvalidationService}.
- * @param name The name of the database file.
- * @param invalidationTracker The {@link InvalidationTracker}
- * @param executor The background executor.
- */
- MultiInstanceInvalidationClient(Context context, String name,
- InvalidationTracker invalidationTracker, Executor executor) {
- mAppContext = context.getApplicationContext();
- mName = name;
- mInvalidationTracker = invalidationTracker;
- mExecutor = executor;
- mObserver = new InvalidationTracker.Observer(invalidationTracker.mTableNames) {
- @Override
- public void onInvalidated(@NonNull Set tables) {
- if (mStopped.get()) {
- return;
- }
- try {
- final IMultiInstanceInvalidationService service = mService;
- if (service != null) {
- service.broadcastInvalidation(mClientId, tables.toArray(new String[0]));
- }
- } catch (RemoteException e) {
- Log.w(Room.LOG_TAG, "Cannot broadcast invalidation", e);
- }
- }
-
- @Override
- boolean isRemote() {
- return true;
- }
- };
- Intent intent = new Intent(mAppContext, MultiInstanceInvalidationService.class);
- mAppContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
- }
-
- void stop() {
- if (mStopped.compareAndSet(false, true)) {
- mExecutor.execute(mTearDownRunnable);
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java b/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java
deleted file mode 100644
index 2e98f120ff..0000000000
--- a/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2018 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.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-import java.util.HashMap;
-
-/**
- * A {@link Service} for remote invalidation among multiple {@link InvalidationTracker} instances.
- * This service runs in the main app process. All the instances of {@link InvalidationTracker}
- * (potentially in other processes) has to connect to this service.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class MultiInstanceInvalidationService extends Service {
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- int mMaxClientId = 0;
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final HashMap mClientNames = new HashMap<>();
-
- // synthetic access
- @SuppressWarnings("WeakerAccess")
- final RemoteCallbackList mCallbackList =
- new RemoteCallbackList() {
- @Override
- public void onCallbackDied(IMultiInstanceInvalidationCallback callback,
- Object cookie) {
- mClientNames.remove((int) cookie);
- }
- };
-
- private final IMultiInstanceInvalidationService.Stub mBinder =
- new IMultiInstanceInvalidationService.Stub() {
-
- // Assigns a client ID to the client.
- @Override
- public int registerCallback(IMultiInstanceInvalidationCallback callback,
- String name) {
- if (name == null) {
- return 0;
- }
- synchronized (mCallbackList) {
- int clientId = ++mMaxClientId;
- // Use the client ID as the RemoteCallbackList cookie.
- if (mCallbackList.register(callback, clientId)) {
- mClientNames.put(clientId, name);
- return clientId;
- } else {
- --mMaxClientId;
- return 0;
- }
- }
- }
-
- // Explicitly removes the client.
- // The client can die without calling this. In that case, mCallbackList
- // .onCallbackDied() can take care of removal.
- @Override
- public void unregisterCallback(IMultiInstanceInvalidationCallback callback,
- int clientId) {
- synchronized (mCallbackList) {
- mCallbackList.unregister(callback);
- mClientNames.remove(clientId);
- }
- }
-
- // Broadcasts table invalidation to other instances of the same database file.
- // The broadcast is not sent to the caller itself.
- @Override
- public void broadcastInvalidation(int clientId, String[] tables) {
- synchronized (mCallbackList) {
- String name = mClientNames.get(clientId);
- if (name == null) {
- Log.w(Room.LOG_TAG, "Remote invalidation client ID not registered");
- return;
- }
- int count = mCallbackList.beginBroadcast();
- try {
- for (int i = 0; i < count; i++) {
- int targetClientId = (int) mCallbackList.getBroadcastCookie(i);
- String targetName = mClientNames.get(targetClientId);
- if (clientId == targetClientId // This is the caller itself.
- || !name.equals(targetName)) { // Not the same file.
- continue;
- }
- try {
- IMultiInstanceInvalidationCallback callback =
- mCallbackList.getBroadcastItem(i);
- callback.onInvalidation(tables);
- } catch (RemoteException e) {
- Log.w(Room.LOG_TAG, "Error invoking a remote callback", e);
- }
- }
- } finally {
- mCallbackList.finishBroadcast();
- }
- }
- }
- };
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-}
diff --git a/app/src/main/java/androidx/room/Room.java b/app/src/main/java/androidx/room/Room.java
deleted file mode 100644
index 2e4dedc7e4..0000000000
--- a/app/src/main/java/androidx/room/Room.java
+++ /dev/null
@@ -1,109 +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 android.content.Context;
-
-import androidx.annotation.NonNull;
-
-/**
- * Utility class for Room.
- */
-@SuppressWarnings("unused")
-public class Room {
- static final String LOG_TAG = "ROOM";
- /**
- * The master table where room keeps its metadata information.
- */
- public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME;
- private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
-
- /**
- * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
- * should keep a reference to it and re-use it.
- *
- * @param context The context for the database. This is usually the Application context.
- * @param klass The abstract class which is annotated with {@link Database} and extends
- * {@link RoomDatabase}.
- * @param name The name of the database file.
- * @param The type of the database class.
- * @return A {@code RoomDatabaseBuilder} which you can use to create the database.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public static RoomDatabase.Builder databaseBuilder(
- @NonNull Context context, @NonNull Class klass, @NonNull String name) {
- //noinspection ConstantConditions
- if (name == null || name.trim().length() == 0) {
- throw new IllegalArgumentException("Cannot build a database with null or empty name."
- + " If you are trying to create an in memory database, use Room"
- + ".inMemoryDatabaseBuilder");
- }
- return new RoomDatabase.Builder<>(context, klass, name);
- }
-
- /**
- * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
- * database disappears when the process is killed.
- * Once a database is built, you should keep a reference to it and re-use it.
- *
- * @param context The context for the database. This is usually the Application context.
- * @param klass The abstract class which is annotated with {@link Database} and extends
- * {@link RoomDatabase}.
- * @param The type of the database class.
- * @return A {@code RoomDatabaseBuilder} which you can use to create the database.
- */
- @NonNull
- public static RoomDatabase.Builder inMemoryDatabaseBuilder(
- @NonNull Context context, @NonNull Class klass) {
- return new RoomDatabase.Builder<>(context, klass, null);
- }
-
- @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
- @NonNull
- static T getGeneratedImplementation(Class klass, String suffix) {
- final String fullPackage = klass.getPackage().getName();
- String name = klass.getCanonicalName();
- final String postPackageName = fullPackage.isEmpty()
- ? name
- : (name.substring(fullPackage.length() + 1));
- final String implName = postPackageName.replace('.', '_') + suffix;
- //noinspection TryWithIdenticalCatches
- try {
-
- @SuppressWarnings("unchecked")
- final Class aClass = (Class) Class.forName(
- fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
- return aClass.newInstance();
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("cannot find implementation for "
- + klass.getCanonicalName() + ". " + implName + " does not exist");
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Cannot access the constructor"
- + klass.getCanonicalName());
- } catch (InstantiationException e) {
- throw new RuntimeException("Failed to create an instance of "
- + klass.getCanonicalName());
- }
- }
-
- /** @deprecated This type should not be instantiated as it contains only static methods. */
- @Deprecated
- @SuppressWarnings("PrivateConstructorForUtilityClass")
- public Room() {
- }
-}
diff --git a/app/src/main/java/androidx/room/RoomDatabase.java b/app/src/main/java/androidx/room/RoomDatabase.java
deleted file mode 100644
index 2e88a2891d..0000000000
--- a/app/src/main/java/androidx/room/RoomDatabase.java
+++ /dev/null
@@ -1,1065 +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 android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Build;
-import android.os.Looper;
-import android.util.Log;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.room.migration.Migration;
-import androidx.room.util.SneakyThrow;
-import androidx.sqlite.db.SimpleSQLiteQuery;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-import androidx.sqlite.db.SupportSQLiteQuery;
-import androidx.sqlite.db.SupportSQLiteStatement;
-import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-/**
- * Base class for all Room databases. All classes that are annotated with {@link Database} must
- * extend this class.
- *
- * RoomDatabase provides direct access to the underlying database implementation but you should
- * prefer using {@link Dao} classes.
- *
- * @see Database
- */
-public abstract class RoomDatabase {
- private static final String DB_IMPL_SUFFIX = "_Impl";
- /**
- * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static final int MAX_BIND_PARAMETER_CNT = 999;
- /**
- * Set by the generated open helper.
- *
- * @deprecated Will be hidden in the next release.
- */
- @Deprecated
- protected volatile SupportSQLiteDatabase mDatabase;
- private Executor mQueryExecutor;
- private Executor mTransactionExecutor;
- private SupportSQLiteOpenHelper mOpenHelper;
- private final InvalidationTracker mInvalidationTracker;
- private boolean mAllowMainThreadQueries;
- boolean mWriteAheadLoggingEnabled;
-
- /**
- * @deprecated Will be hidden in the next release.
- */
- @Nullable
- @Deprecated
- protected List mCallbacks;
-
- private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
-
- /**
- * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
- * querying database updates.
- *
- * The returned lock is reentrant and will allow multiple threads to acquire the lock
- * simultaneously until {@link #close()} is invoked in which the lock becomes exclusive as
- * a way to let the InvalidationTracker finish its work before closing the database.
- *
- * @return The lock for {@link #close()}.
- */
- Lock getCloseLock() {
- return mCloseLock.readLock();
- }
-
- /**
- * This id is only set on threads that are used to dispatch coroutines within a suspending
- * database transaction.
- */
- private final ThreadLocal mSuspendingTransactionId = new ThreadLocal<>();
-
- /**
- * Gets the suspending transaction id of the current thread.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- ThreadLocal getSuspendingTransactionId() {
- return mSuspendingTransactionId;
- }
-
-
- private final Map mBackingFieldMap = new ConcurrentHashMap<>();
-
- /**
- * Gets the map for storing extension properties of Kotlin type.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- Map getBackingFieldMap() {
- return mBackingFieldMap;
- }
-
- /**
- * Creates a RoomDatabase.
- *
- * You cannot create an instance of a database, instead, you should acquire it via
- * {@link Room#databaseBuilder(Context, Class, String)} or
- * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
- */
- public RoomDatabase() {
- mInvalidationTracker = createInvalidationTracker();
- }
-
- /**
- * Called by {@link Room} when it is initialized.
- *
- * @param configuration The database configuration.
- */
- @CallSuper
- public void init(@NonNull DatabaseConfiguration configuration) {
- mOpenHelper = createOpenHelper(configuration);
- if (mOpenHelper instanceof SQLiteCopyOpenHelper) {
- SQLiteCopyOpenHelper copyOpenHelper = (SQLiteCopyOpenHelper) mOpenHelper;
- copyOpenHelper.setDatabaseConfiguration(configuration);
- }
- boolean wal = false;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
- mOpenHelper.setWriteAheadLoggingEnabled(wal);
- }
- mCallbacks = configuration.callbacks;
- mQueryExecutor = configuration.queryExecutor;
- mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
- mAllowMainThreadQueries = configuration.allowMainThreadQueries;
- mWriteAheadLoggingEnabled = wal;
- if (configuration.multiInstanceInvalidation) {
- mInvalidationTracker.startMultiInstanceInvalidation(configuration.context,
- configuration.name);
- }
- }
-
- /**
- * Returns the SQLite open helper used by this database.
- *
- * @return The SQLite open helper used by this database.
- */
- @NonNull
- public SupportSQLiteOpenHelper getOpenHelper() {
- return mOpenHelper;
- }
-
- /**
- * Creates the open helper to access the database. Generated class already implements this
- * method.
- * Note that this method is called when the RoomDatabase is initialized.
- *
- * @param config The configuration of the Room database.
- * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
- */
- @NonNull
- protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
-
- /**
- * Called when the RoomDatabase is created.
- *
- * This is already implemented by the generated code.
- *
- * @return Creates a new InvalidationTracker.
- */
- @NonNull
- protected abstract InvalidationTracker createInvalidationTracker();
-
- /**
- * Deletes all rows from all the tables that are registered to this database as
- * {@link Database#entities()}.
- *
- * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}.
- *
- * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the
- * data is completely erased. The space will be reclaimed by the system if the amount surpasses
- * the threshold of database file size.
- *
- * @see Database File Format
- */
- @WorkerThread
- public abstract void clearAllTables();
-
- /**
- * Returns true if database connection is open and initialized.
- *
- * @return true if the database connection is open, false otherwise.
- */
- public boolean isOpen() {
- final SupportSQLiteDatabase db = mDatabase;
- return db != null && db.isOpen();
- }
-
- /**
- * Closes the database if it is already open.
- */
- public void close() {
- if (isOpen()) {
- final Lock closeLock = mCloseLock.writeLock();
- try {
- closeLock.lock();
- mInvalidationTracker.stopMultiInstanceInvalidation();
- mOpenHelper.close();
- } finally {
- closeLock.unlock();
- }
- }
- }
-
- /**
- * Asserts that we are not on the main thread.
- *
- * @hide
- */
- @SuppressWarnings("WeakerAccess")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- // used in generated code
- public void assertNotMainThread() {
- if (mAllowMainThreadQueries) {
- return;
- }
- if (isMainThread()) {
- throw new IllegalStateException("Cannot access database on the main thread since"
- + " it may potentially lock the UI for a long period of time.");
- }
- }
-
- /**
- * Asserts that we are not on a suspending transaction.
- *
- * @hide
- */
- @SuppressWarnings("WeakerAccess")
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- // used in generated code
- public void assertNotSuspendingTransaction() {
- if (!inTransaction() && mSuspendingTransactionId.get() != null) {
- throw new IllegalStateException("Cannot access database on a different coroutine"
- + " context inherited from a suspending transaction.");
- }
- }
-
- // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
- // methods we are using and also helps unit tests to mock this class without mocking
- // all SQLite database methods.
-
- /**
- * Convenience method to query the database with arguments.
- *
- * @param query The sql query
- * @param args The bind arguments for the placeholders in the query
- * @return A Cursor obtained by running the given query in the Room database.
- */
- public Cursor query(String query, @Nullable Object[] args) {
- return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
- }
-
- /**
- * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
- *
- * @param query The Query which includes the SQL and a bind callback for bind arguments.
- * @return Result of the query.
- */
- public Cursor query(SupportSQLiteQuery query) {
- assertNotMainThread();
- assertNotSuspendingTransaction();
- return mOpenHelper.getWritableDatabase().query(query);
- }
-
- /**
- * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
- *
- * @param sql The query to compile.
- * @return The compiled query.
- */
- public SupportSQLiteStatement compileStatement(@NonNull String sql) {
- assertNotMainThread();
- assertNotSuspendingTransaction();
- return mOpenHelper.getWritableDatabase().compileStatement(sql);
- }
-
- /**
- * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
- *
- * @deprecated Use {@link #runInTransaction(Runnable)}
- */
- @Deprecated
- public void beginTransaction() {
- assertNotMainThread();
- SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
- mInvalidationTracker.syncTriggers(database);
- database.beginTransaction();
- }
-
- /**
- * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
- *
- * @deprecated Use {@link #runInTransaction(Runnable)}
- */
- @Deprecated
- public void endTransaction() {
- mOpenHelper.getWritableDatabase().endTransaction();
- if (!inTransaction()) {
- // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
- // endTransaction call to do it.
- mInvalidationTracker.refreshVersionsAsync();
- }
- }
-
- /**
- * @return The Executor in use by this database for async queries.
- */
- @NonNull
- public Executor getQueryExecutor() {
- return mQueryExecutor;
- }
-
- /**
- * @return The Executor in use by this database for async transactions.
- */
- @NonNull
- public Executor getTransactionExecutor() {
- return mTransactionExecutor;
- }
-
- /**
- * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
- *
- * @deprecated Use {@link #runInTransaction(Runnable)}
- */
- @Deprecated
- public void setTransactionSuccessful() {
- mOpenHelper.getWritableDatabase().setTransactionSuccessful();
- }
-
- /**
- * Executes the specified {@link Runnable} in a database transaction. The transaction will be
- * marked as successful unless an exception is thrown in the {@link Runnable}.
- *
- * Room will only perform at most one transaction at a time.
- *
- * @param body The piece of code to execute.
- */
- @SuppressWarnings("deprecation")
- public void runInTransaction(@NonNull Runnable body) {
- beginTransaction();
- try {
- body.run();
- setTransactionSuccessful();
- } finally {
- endTransaction();
- }
- }
-
- /**
- * Executes the specified {@link Callable} in a database transaction. The transaction will be
- * marked as successful unless an exception is thrown in the {@link Callable}.
- *
- * Room will only perform at most one transaction at a time.
- *
- * @param body The piece of code to execute.
- * @param The type of the return value.
- * @return The value returned from the {@link Callable}.
- */
- @SuppressWarnings("deprecation")
- public V runInTransaction(@NonNull Callable body) {
- beginTransaction();
- try {
- V result = body.call();
- setTransactionSuccessful();
- return result;
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- SneakyThrow.reThrow(e);
- return null; // Unreachable code, but compiler doesn't know it.
- } finally {
- endTransaction();
- }
- }
-
- /**
- * Called by the generated code when database is open.
- *
- * You should never call this method manually.
- *
- * @param db The database instance.
- */
- protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
- mInvalidationTracker.internalInit(db);
- }
-
- /**
- * Returns the invalidation tracker for this database.
- *
- * You can use the invalidation tracker to get notified when certain tables in the database
- * are modified.
- *
- * @return The invalidation tracker for the database.
- */
- @NonNull
- public InvalidationTracker getInvalidationTracker() {
- return mInvalidationTracker;
- }
-
- /**
- * Returns true if current thread is in a transaction.
- *
- * @return True if there is an active transaction in current thread, false otherwise.
- * @see SupportSQLiteDatabase#inTransaction()
- */
- @SuppressWarnings("WeakerAccess")
- public boolean inTransaction() {
- return mOpenHelper.getWritableDatabase().inTransaction();
- }
-
- /**
- * Journal modes for SQLite database.
- *
- * @see RoomDatabase.Builder#setJournalMode(JournalMode)
- */
- public enum JournalMode {
-
- /**
- * Let Room choose the journal mode. This is the default value when no explicit value is
- * specified.
- *
- * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16
- * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used.
- */
- AUTOMATIC,
-
- /**
- * Truncate journal mode.
- */
- TRUNCATE,
-
- /**
- * Write-Ahead Logging mode.
- */
- @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
- WRITE_AHEAD_LOGGING;
-
- /**
- * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
- * {@link #WRITE_AHEAD_LOGGING}.
- */
- @SuppressLint("NewApi")
- JournalMode resolve(Context context) {
- if (this != AUTOMATIC) {
- return this;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- ActivityManager manager = (ActivityManager)
- context.getSystemService(Context.ACTIVITY_SERVICE);
- if (manager != null && !isLowRamDevice(manager)) {
- return WRITE_AHEAD_LOGGING;
- }
- }
- return TRUNCATE;
- }
-
- private static boolean isLowRamDevice(@NonNull ActivityManager activityManager) {
- if (Build.VERSION.SDK_INT >= 19) {
- return activityManager.isLowRamDevice();
- }
- return false;
- }
- }
-
- /**
- * Builder for RoomDatabase.
- *
- * @param The type of the abstract database class.
- */
- public static class Builder {
- private final Class mDatabaseClass;
- private final String mName;
- private final Context mContext;
- private ArrayList mCallbacks;
-
- /** The Executor used to run database queries. This should be background-threaded. */
- private Executor mQueryExecutor;
- /** The Executor used to run database transactions. This should be background-threaded. */
- private Executor mTransactionExecutor;
- private SupportSQLiteOpenHelper.Factory mFactory;
- private boolean mAllowMainThreadQueries;
- private JournalMode mJournalMode;
- private boolean mMultiInstanceInvalidation;
- private boolean mRequireMigration;
- private boolean mAllowDestructiveMigrationOnDowngrade;
- /**
- * Migrations, mapped by from-to pairs.
- */
- private final MigrationContainer mMigrationContainer;
- private Set mMigrationsNotRequiredFrom;
- /**
- * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in
- * {@link #addMigrations(Migration...)} for later validation that makes those versions don't
- * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(int...)}.
- */
- private Set mMigrationStartAndEndVersions;
-
- private String mCopyFromAssetPath;
- private File mCopyFromFile;
-
- Builder(@NonNull Context context, @NonNull Class klass, @Nullable String name) {
- mContext = context;
- mDatabaseClass = klass;
- mName = name;
- mJournalMode = JournalMode.AUTOMATIC;
- mRequireMigration = true;
- mMigrationContainer = new MigrationContainer();
- }
-
- /**
- * Configures Room to create and open the database using a pre-packaged database located in
- * the application 'assets/' folder.
- *
- * Room does not open the pre-packaged database, instead it copies it into the internal
- * app database folder and then opens it. The pre-packaged database file must be located in
- * the "assets/" folder of your application. For example, the path for a file located in
- * "assets/databases/products.db" would be "databases/products.db".
- *
- * The pre-packaged database schema will be validated. It might be best to create your
- * pre-packaged database schema utilizing the exported schema files generated when
- * {@link Database#exportSchema()} is enabled.
- *
- * This method is not supported for an in memory database {@link Builder}.
- *
- * @param databaseFilePath The file path within the 'assets/' directory of where the
- * database file is located.
- *
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder createFromAsset(@NonNull String databaseFilePath) {
- mCopyFromAssetPath = databaseFilePath;
- return this;
- }
-
- /**
- * Configures Room to create and open the database using a pre-packaged database file.
- *
- * Room does not open the pre-packaged database, instead it copies it into the internal
- * app database folder and then opens it. The given file must be accessible and the right
- * permissions must be granted for Room to copy the file.
- *
- * The pre-packaged database schema will be validated. It might be best to create your
- * pre-packaged database schema utilizing the exported schema files generated when
- * {@link Database#exportSchema()} is enabled.
- *
- * This method is not supported for an in memory database {@link Builder}.
- *
- * @param databaseFile The database file.
- *
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder createFromFile(@NonNull File databaseFile) {
- mCopyFromFile = databaseFile;
- return this;
- }
-
- /**
- * Sets the database factory. If not set, it defaults to
- * {@link FrameworkSQLiteOpenHelperFactory}.
- *
- * @param factory The factory to use to access the database.
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
- mFactory = factory;
- return this;
- }
-
- /**
- * Adds a migration to the builder.
- *
- * Each Migration has a start and end versions and Room runs these migrations to bring the
- * database to the latest version.
- *
- * If a migration item is missing between current version and the latest version, Room
- * will clear the database and recreate so even if you have no changes between 2 versions,
- * you should still provide a Migration object to the builder.
- *
- * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
- * going version 3 to 5 without going to version 4). If Room opens a database at version
- * 3 and latest version is >= 5, Room will use the migration object that can migrate from
- * 3 to 5 instead of 3 to 4 and 4 to 5.
- *
- * @param migrations The migration object that can modify the database and to the necessary
- * changes.
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder addMigrations(@NonNull Migration... migrations) {
- if (mMigrationStartAndEndVersions == null) {
- mMigrationStartAndEndVersions = new HashSet<>();
- }
- for (Migration migration : migrations) {
- mMigrationStartAndEndVersions.add(migration.startVersion);
- mMigrationStartAndEndVersions.add(migration.endVersion);
- }
-
- mMigrationContainer.addMigrations(migrations);
- return this;
- }
-
- /**
- * Disables the main thread query check for Room.
- *
- * Room ensures that Database is never accessed on the main thread because it may lock the
- * main thread and trigger an ANR. If you need to access the database from the main thread,
- * you should always use async alternatives or manually move the call to a background
- * thread.
- *
- * You may want to turn this check off for testing.
- *
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder allowMainThreadQueries() {
- mAllowMainThreadQueries = true;
- return this;
- }
-
- /**
- * Sets the journal mode for this database.
- *
- *
- * This value is ignored if the builder is initialized with
- * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
- *
- * The journal mode should be consistent across multiple instances of
- * {@link RoomDatabase} for a single SQLite database file.
- *
- * The default value is {@link JournalMode#AUTOMATIC}.
- *
- * @param journalMode The journal mode.
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder setJournalMode(@NonNull JournalMode journalMode) {
- mJournalMode = journalMode;
- return this;
- }
-
- /**
- * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous
- * queries and tasks, including {@code LiveData} invalidation, {@code Flowable} scheduling
- * and {@code ListenableFuture} tasks.
- *
- * When both the query executor and transaction executor are unset, then a default
- * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
- * amongst Architecture Components libraries. If the query executor is unset but a
- * transaction executor was set, then the same {@code Executor} will be used for queries.
- *
- * For best performance the given {@code Executor} should be bounded (max number of threads
- * is limited).
- *
- * The input {@code Executor} cannot run tasks on the UI thread.
- **
- * @return This {@link Builder} instance.
- *
- * @see #setTransactionExecutor(Executor)
- */
- @NonNull
- public Builder setQueryExecutor(@NonNull Executor executor) {
- mQueryExecutor = executor;
- return this;
- }
-
- /**
- * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous
- * transaction queries and tasks, including {@code LiveData} invalidation, {@code Flowable}
- * scheduling and {@code ListenableFuture} tasks.
- *
- * When both the transaction executor and query executor are unset, then a default
- * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
- * amongst Architecture Components libraries. If the transaction executor is unset but a
- * query executor was set, then the same {@code Executor} will be used for transactions.
- *
- * If the given {@code Executor} is shared then it should be unbounded to avoid the
- * possibility of a deadlock. Room will not use more than one thread at a time from this
- * executor since only one transaction at a time can be executed, other transactions will
- * be queued on a first come, first serve order.
- *
- * The input {@code Executor} cannot run tasks on the UI thread.
- *
- * @return This {@link Builder} instance.
- *
- * @see #setQueryExecutor(Executor)
- */
- @NonNull
- public Builder setTransactionExecutor(@NonNull Executor executor) {
- mTransactionExecutor = executor;
- return this;
- }
-
- /**
- * Sets whether table invalidation in this instance of {@link RoomDatabase} should be
- * broadcast and synchronized with other instances of the same {@link RoomDatabase},
- * including those in a separate process. In order to enable multi-instance invalidation,
- * this has to be turned on both ends.
- *
- * This is not enabled by default.
- *
- * This does not work for in-memory databases. This does not work between database instances
- * targeting different database files.
- *
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder enableMultiInstanceInvalidation() {
- mMultiInstanceInvalidation = mName != null;
- return this;
- }
-
- /**
- * Allows Room to destructively recreate database tables if {@link Migration}s that would
- * migrate old database schemas to the latest schema version are not found.
- *
- * When the database version on the device does not match the latest schema version, Room
- * runs necessary {@link Migration}s on the database.
- *
- * If it cannot find the set of {@link Migration}s that will bring the database to the
- * current version, it will throw an {@link IllegalStateException}.
- *
- * You can call this method to change this behavior to re-create the database instead of
- * crashing.
- *
- * If the database was create from an asset or a file then Room will try to use the same
- * file to re-create the database, otherwise this will delete all of the data in the
- * database tables managed by Room.
- *
- * To let Room fallback to destructive migration only during a schema downgrade then use
- * {@link #fallbackToDestructiveMigrationOnDowngrade()}.
- *
- * @return This {@link Builder} instance.
- *
- * @see #fallbackToDestructiveMigrationOnDowngrade()
- */
- @NonNull
- public Builder fallbackToDestructiveMigration() {
- mRequireMigration = false;
- mAllowDestructiveMigrationOnDowngrade = true;
- return this;
- }
-
- /**
- * Allows Room to destructively recreate database tables if {@link Migration}s are not
- * available when downgrading to old schema versions.
- *
- * @return This {@link Builder} instance.
- *
- * @see Builder#fallbackToDestructiveMigration()
- */
- @NonNull
- public Builder fallbackToDestructiveMigrationOnDowngrade() {
- mRequireMigration = true;
- mAllowDestructiveMigrationOnDowngrade = true;
- return this;
- }
-
- /**
- * Informs Room that it is allowed to destructively recreate database tables from specific
- * starting schema versions.
- *
- * This functionality is the same as that provided by
- * {@link #fallbackToDestructiveMigration()}, except that this method allows the
- * specification of a set of schema versions for which destructive recreation is allowed.
- *
- * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want
- * to allow destructive migrations from some schema versions while still taking advantage
- * of exceptions being thrown due to unintentionally missing migrations.
- *
- * Note: No versions passed to this method may also exist as either starting or ending
- * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a
- * version passed to this method is found as a starting or ending version in a Migration, an
- * exception will be thrown.
- *
- * @param startVersions The set of schema versions from which Room should use a destructive
- * migration.
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder fallbackToDestructiveMigrationFrom(int... startVersions) {
- if (mMigrationsNotRequiredFrom == null) {
- mMigrationsNotRequiredFrom = new HashSet<>(startVersions.length);
- }
- for (int startVersion : startVersions) {
- mMigrationsNotRequiredFrom.add(startVersion);
- }
- return this;
- }
-
- /**
- * Adds a {@link Callback} to this database.
- *
- * @param callback The callback.
- * @return This {@link Builder} instance.
- */
- @NonNull
- public Builder addCallback(@NonNull Callback callback) {
- if (mCallbacks == null) {
- mCallbacks = new ArrayList<>();
- }
- mCallbacks.add(callback);
- return this;
- }
-
- /**
- * Creates the databases and initializes it.
- *
- * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
- * triggers.
- *
- * @return A new database instance.
- */
- @SuppressLint("RestrictedApi")
- @NonNull
- public T build() {
- //noinspection ConstantConditions
- if (mContext == null) {
- throw new IllegalArgumentException("Cannot provide null context for the database.");
- }
- //noinspection ConstantConditions
- if (mDatabaseClass == null) {
- throw new IllegalArgumentException("Must provide an abstract class that"
- + " extends RoomDatabase");
- }
- if (mQueryExecutor == null && mTransactionExecutor == null) {
- mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor();
- } else if (mQueryExecutor != null && mTransactionExecutor == null) {
- mTransactionExecutor = mQueryExecutor;
- } else if (mQueryExecutor == null && mTransactionExecutor != null) {
- mQueryExecutor = mTransactionExecutor;
- }
-
- if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
- for (Integer version : mMigrationStartAndEndVersions) {
- if (mMigrationsNotRequiredFrom.contains(version)) {
- throw new IllegalArgumentException(
- "Inconsistency detected. A Migration was supplied to "
- + "addMigration(Migration... migrations) that has a start "
- + "or end version equal to a start version supplied to "
- + "fallbackToDestructiveMigrationFrom(int... "
- + "startVersions). Start version: "
- + version);
- }
- }
- }
-
- if (mFactory == null) {
- mFactory = new FrameworkSQLiteOpenHelperFactory();
- }
-
- if (mCopyFromAssetPath != null || mCopyFromFile != null) {
- if (mName == null) {
- throw new IllegalArgumentException("Cannot create from asset or file for an "
- + "in-memory database.");
- }
- if (mCopyFromAssetPath != null && mCopyFromFile != null) {
- throw new IllegalArgumentException("Both createFromAsset() and "
- + "createFromFile() was called on this Builder but the database can "
- + "only be created using one of the two configurations.");
- }
- mFactory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile,
- mFactory);
- }
- DatabaseConfiguration configuration =
- new DatabaseConfiguration(
- mContext,
- mName,
- mFactory,
- mMigrationContainer,
- mCallbacks,
- mAllowMainThreadQueries,
- mJournalMode.resolve(mContext),
- mQueryExecutor,
- mTransactionExecutor,
- mMultiInstanceInvalidation,
- mRequireMigration,
- mAllowDestructiveMigrationOnDowngrade,
- mMigrationsNotRequiredFrom,
- mCopyFromAssetPath,
- mCopyFromFile);
- T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
- db.init(configuration);
- return db;
- }
- }
-
- /**
- * A container to hold migrations. It also allows querying its contents to find migrations
- * between two versions.
- */
- public static class MigrationContainer {
- private HashMap> mMigrations = new HashMap<>();
-
- /**
- * Adds the given migrations to the list of available migrations. If 2 migrations have the
- * same start-end versions, the latter migration overrides the previous one.
- *
- * @param migrations List of available migrations.
- */
- public void addMigrations(@NonNull Migration... migrations) {
- for (Migration migration : migrations) {
- addMigration(migration);
- }
- }
-
- private void addMigration(Migration migration) {
- final int start = migration.startVersion;
- final int end = migration.endVersion;
- TreeMap targetMap = mMigrations.get(start);
- if (targetMap == null) {
- targetMap = new TreeMap<>();
- mMigrations.put(start, targetMap);
- }
- Migration existing = targetMap.get(end);
- if (existing != null) {
- Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
- }
- targetMap.put(end, migration);
- }
-
- /**
- * Finds the list of migrations that should be run to move from {@code start} version to
- * {@code end} version.
- *
- * @param start The current database version
- * @param end The target database version
- * @return An ordered list of {@link Migration} objects that should be run to migrate
- * between the given versions. If a migration path cannot be found, returns {@code null}.
- */
- @SuppressWarnings("WeakerAccess")
- @Nullable
- public List findMigrationPath(int start, int end) {
- if (start == end) {
- return Collections.emptyList();
- }
- boolean migrateUp = end > start;
- List result = new ArrayList<>();
- return findUpMigrationPath(result, migrateUp, start, end);
- }
-
- private List findUpMigrationPath(List result, boolean upgrade,
- int start, int end) {
- while (upgrade ? start < end : start > end) {
- TreeMap targetNodes = mMigrations.get(start);
- if (targetNodes == null) {
- return null;
- }
- // keys are ordered so we can start searching from one end of them.
- Set keySet;
- if (upgrade) {
- keySet = targetNodes.descendingKeySet();
- } else {
- keySet = targetNodes.keySet();
- }
- boolean found = false;
- for (int targetVersion : keySet) {
- final boolean shouldAddToPath;
- if (upgrade) {
- shouldAddToPath = targetVersion <= end && targetVersion > start;
- } else {
- shouldAddToPath = targetVersion >= end && targetVersion < start;
- }
- if (shouldAddToPath) {
- result.add(targetNodes.get(targetVersion));
- start = targetVersion;
- found = true;
- break;
- }
- }
- if (!found) {
- return null;
- }
- }
- return result;
- }
- }
-
- /** Returns true if the calling thread is the main thread. */
- private static boolean isMainThread() {
- return Looper.getMainLooper().getThread() == Thread.currentThread();
- }
-
- /**
- * Callback for {@link RoomDatabase}.
- */
- public abstract static class Callback {
-
- /**
- * Called when the database is created for the first time. This is called after all the
- * tables are created.
- *
- * @param db The database.
- */
- public void onCreate(@NonNull SupportSQLiteDatabase db) {
- }
-
- /**
- * Called when the database has been opened.
- *
- * @param db The database.
- */
- public void onOpen(@NonNull SupportSQLiteDatabase db) {
- }
-
- /**
- * Called after the database was destructively migrated
- *
- * @param db The database.
- */
- public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db){
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/RoomOpenHelper.java b/app/src/main/java/androidx/room/RoomOpenHelper.java
deleted file mode 100644
index 3dbe7ddeac..0000000000
--- a/app/src/main/java/androidx/room/RoomOpenHelper.java
+++ /dev/null
@@ -1,277 +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 android.database.Cursor;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.room.migration.Migration;
-import androidx.sqlite.db.SimpleSQLiteQuery;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-
-import java.util.List;
-
-/**
- * An open helper that holds a reference to the configuration until the database is opened.
- *
- * @hide
- */
-@SuppressWarnings("unused")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
- @Nullable
- private DatabaseConfiguration mConfiguration;
- @NonNull
- private final Delegate mDelegate;
- @NonNull
- private final String mIdentityHash;
- /**
- * Room v1 had a bug where the hash was not consistent if fields are reordered.
- * The new has fixes it but we still need to accept the legacy hash.
- */
- @NonNull // b/64290754
- private final String mLegacyHash;
-
- public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
- @NonNull String identityHash, @NonNull String legacyHash) {
- super(delegate.version);
- mConfiguration = configuration;
- mDelegate = delegate;
- mIdentityHash = identityHash;
- mLegacyHash = legacyHash;
- }
-
- public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
- @NonNull String legacyHash) {
- this(configuration, delegate, "", legacyHash);
- }
-
- @Override
- public void onConfigure(SupportSQLiteDatabase db) {
- super.onConfigure(db);
- }
-
- @Override
- public void onCreate(SupportSQLiteDatabase db) {
- boolean isEmptyDatabase = hasEmptySchema(db);
- mDelegate.createAllTables(db);
- if (!isEmptyDatabase) {
- // A 0 version pre-populated database goes through the create path because the
- // framework's SQLiteOpenHelper thinks the database was just created from scratch. If we
- // find the database not to be empty, then it is a pre-populated, we must validate it to
- // see if its suitable for usage.
- ValidationResult result = mDelegate.onValidateSchema(db);
- if (!result.isValid) {
- throw new IllegalStateException("Pre-packaged database has an invalid schema: "
- + result.expectedFoundMsg);
- }
- }
- updateIdentity(db);
- mDelegate.onCreate(db);
- }
-
- @Override
- public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
- boolean migrated = false;
- if (mConfiguration != null) {
- List migrations = mConfiguration.migrationContainer.findMigrationPath(
- oldVersion, newVersion);
- if (migrations != null) {
- mDelegate.onPreMigrate(db);
- for (Migration migration : migrations) {
- migration.migrate(db);
- }
- ValidationResult result = mDelegate.onValidateSchema(db);
- if (!result.isValid) {
- throw new IllegalStateException("Migration didn't properly handle: "
- + result.expectedFoundMsg);
- }
- mDelegate.onPostMigrate(db);
- updateIdentity(db);
- migrated = true;
- }
- }
- if (!migrated) {
- if (mConfiguration != null
- && !mConfiguration.isMigrationRequired(oldVersion, newVersion)) {
- mDelegate.dropAllTables(db);
- mDelegate.createAllTables(db);
- } else {
- throw new IllegalStateException("A migration from " + oldVersion + " to "
- + newVersion + " was required but not found. Please provide the "
- + "necessary Migration path via "
- + "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
- + "destructive migrations via one of the "
- + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
- }
- }
- }
-
- @Override
- public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
- onUpgrade(db, oldVersion, newVersion);
- }
-
- @Override
- public void onOpen(SupportSQLiteDatabase db) {
- super.onOpen(db);
- checkIdentity(db);
- mDelegate.onOpen(db);
- // there might be too many configurations etc, just clear it.
- mConfiguration = null;
- }
-
- private void checkIdentity(SupportSQLiteDatabase db) {
- if (hasRoomMasterTable(db)) {
- String identityHash = null;
- Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
- //noinspection TryFinallyCanBeTryWithResources
- try {
- if (cursor.moveToFirst()) {
- identityHash = cursor.getString(0);
- }
- } finally {
- cursor.close();
- }
- if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
- throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
- + " you've changed schema but forgot to update the version number. You can"
- + " simply fix this by increasing the version number.");
- }
- } else {
- // No room_master_table, this might an a pre-populated DB, we must validate to see if
- // its suitable for usage.
- ValidationResult result = mDelegate.onValidateSchema(db);
- if (!result.isValid) {
- throw new IllegalStateException("Pre-packaged database has an invalid schema: "
- + result.expectedFoundMsg);
- }
- mDelegate.onPostMigrate(db);
- updateIdentity(db);
- }
- }
-
- private void updateIdentity(SupportSQLiteDatabase db) {
- createMasterTableIfNotExists(db);
- db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
- }
-
- private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
- db.execSQL(RoomMasterTable.CREATE_QUERY);
- }
-
- private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
- Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
- + RoomMasterTable.TABLE_NAME + "'");
- //noinspection TryFinallyCanBeTryWithResources
- try {
- return cursor.moveToFirst() && cursor.getInt(0) != 0;
- } finally {
- cursor.close();
- }
- }
-
- private static boolean hasEmptySchema(SupportSQLiteDatabase db) {
- Cursor cursor = db.query(
- "SELECT count(*) FROM sqlite_master WHERE name != 'android_metadata'");
- //noinspection TryFinallyCanBeTryWithResources
- try {
- return cursor.moveToFirst() && cursor.getInt(0) == 0;
- } finally {
- cursor.close();
- }
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public abstract static class Delegate {
- public final int version;
-
- public Delegate(int version) {
- this.version = version;
- }
-
- protected abstract void dropAllTables(SupportSQLiteDatabase database);
-
- protected abstract void createAllTables(SupportSQLiteDatabase database);
-
- protected abstract void onOpen(SupportSQLiteDatabase database);
-
- protected abstract void onCreate(SupportSQLiteDatabase database);
-
- /**
- * Called after a migration run to validate database integrity.
- *
- * @param db The SQLite database.
- *
- * @deprecated Use {@link #onValidateSchema(SupportSQLiteDatabase)}
- */
- @Deprecated
- protected void validateMigration(SupportSQLiteDatabase db) {
- throw new UnsupportedOperationException("validateMigration is deprecated");
- }
-
- /**
- * Called after a migration run or pre-package database copy to validate database integrity.
- *
- * @param db The SQLite database.
- */
- @SuppressWarnings("deprecation")
- @NonNull
- protected ValidationResult onValidateSchema(@NonNull SupportSQLiteDatabase db) {
- validateMigration(db);
- return new ValidationResult(true, null);
- }
-
- /**
- * Called before migrations execute to perform preliminary work.
- * @param database The SQLite database.
- */
- protected void onPreMigrate(SupportSQLiteDatabase database) {
-
- }
-
- /**
- * Called after migrations execute to perform additional work.
- * @param database The SQLite database.
- */
- protected void onPostMigrate(SupportSQLiteDatabase database) {
-
- }
- }
-
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static class ValidationResult {
-
- public final boolean isValid;
- @Nullable
- public final String expectedFoundMsg;
-
- public ValidationResult(boolean isValid, @Nullable String expectedFoundMsg) {
- this.isValid = isValid;
- this.expectedFoundMsg = expectedFoundMsg;
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/RoomSQLiteQuery.java b/app/src/main/java/androidx/room/RoomSQLiteQuery.java
deleted file mode 100644
index 689352c032..0000000000
--- a/app/src/main/java/androidx/room/RoomSQLiteQuery.java
+++ /dev/null
@@ -1,299 +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.IntDef;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.sqlite.db.SupportSQLiteProgram;
-import androidx.sqlite.db.SupportSQLiteQuery;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * This class is used as an intermediate place to keep binding arguments so that we can run
- * Cursor queries with correct types rather than passing everything as a string.
- *
- * Because it is relatively a big object, they are pooled and must be released after each use.
- *
- * @hide
- */
-@SuppressWarnings("unused")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- // Maximum number of queries we'll keep cached.
- static final int POOL_LIMIT = 15;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
- // clear the bigger queries (# of arguments).
- static final int DESIRED_POOL_SIZE = 10;
- private volatile String mQuery;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final long[] mLongBindings;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final double[] mDoubleBindings;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final String[] mStringBindings;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final byte[][] mBlobBindings;
-
- @Binding
- private final int[] mBindingTypes;
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- final int mCapacity;
- // number of arguments in the query
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- int mArgCount;
-
-
- @SuppressWarnings("WeakerAccess")
- @VisibleForTesting
- static final TreeMap sQueryPool = new TreeMap<>();
-
- /**
- * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
- *
- * @param supportSQLiteQuery The query to copy from
- * @return A new query copied from the provided one.
- */
- public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) {
- final RoomSQLiteQuery query = RoomSQLiteQuery.acquire(
- supportSQLiteQuery.getSql(),
- supportSQLiteQuery.getArgCount());
- supportSQLiteQuery.bindTo(new SupportSQLiteProgram() {
- @Override
- public void bindNull(int index) {
- query.bindNull(index);
- }
-
- @Override
- public void bindLong(int index, long value) {
- query.bindLong(index, value);
- }
-
- @Override
- public void bindDouble(int index, double value) {
- query.bindDouble(index, value);
- }
-
- @Override
- public void bindString(int index, String value) {
- query.bindString(index, value);
- }
-
- @Override
- public void bindBlob(int index, byte[] value) {
- query.bindBlob(index, value);
- }
-
- @Override
- public void clearBindings() {
- query.clearBindings();
- }
-
- @Override
- public void close() {
- // ignored.
- }
- });
- return query;
- }
-
- /**
- * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
- * given query.
- *
- * @param query The query to prepare
- * @param argumentCount The number of query arguments
- * @return A RoomSQLiteQuery that holds the given query and has space for the given number of
- * arguments.
- */
- @SuppressWarnings("WeakerAccess")
- public static RoomSQLiteQuery acquire(String query, int argumentCount) {
- synchronized (sQueryPool) {
- final Map.Entry entry =
- sQueryPool.ceilingEntry(argumentCount);
- if (entry != null) {
- sQueryPool.remove(entry.getKey());
- final RoomSQLiteQuery sqliteQuery = entry.getValue();
- sqliteQuery.init(query, argumentCount);
- return sqliteQuery;
- }
- }
- RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
- sqLiteQuery.init(query, argumentCount);
- return sqLiteQuery;
- }
-
- private RoomSQLiteQuery(int capacity) {
- mCapacity = capacity;
- // because, 1 based indices... we don't want to offsets everything with 1 all the time.
- int limit = capacity + 1;
- //noinspection WrongConstant
- mBindingTypes = new int[limit];
- mLongBindings = new long[limit];
- mDoubleBindings = new double[limit];
- mStringBindings = new String[limit];
- mBlobBindings = new byte[limit][];
- }
-
- @SuppressWarnings("WeakerAccess")
- void init(String query, int argCount) {
- mQuery = query;
- mArgCount = argCount;
- }
-
- /**
- * Releases the query back to the pool.
- *
- * After released, the statement might be returned when {@link #acquire(String, int)} is called
- * so you should never re-use it after releasing.
- */
- @SuppressWarnings("WeakerAccess")
- public void release() {
- synchronized (sQueryPool) {
- sQueryPool.put(mCapacity, this);
- prunePoolLocked();
- }
- }
-
- private static void prunePoolLocked() {
- if (sQueryPool.size() > POOL_LIMIT) {
- int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
- final Iterator iterator = sQueryPool.descendingKeySet().iterator();
- while (toBeRemoved-- > 0) {
- iterator.next();
- iterator.remove();
- }
- }
- }
-
- @Override
- public String getSql() {
- return mQuery;
- }
-
- @Override
- public int getArgCount() {
- return mArgCount;
- }
-
- @Override
- public void bindTo(SupportSQLiteProgram program) {
- for (int index = 1; index <= mArgCount; index++) {
- switch (mBindingTypes[index]) {
- case NULL:
- program.bindNull(index);
- break;
- case LONG:
- program.bindLong(index, mLongBindings[index]);
- break;
- case DOUBLE:
- program.bindDouble(index, mDoubleBindings[index]);
- break;
- case STRING:
- program.bindString(index, mStringBindings[index]);
- break;
- case BLOB:
- program.bindBlob(index, mBlobBindings[index]);
- break;
- }
- }
- }
-
- @Override
- public void bindNull(int index) {
- mBindingTypes[index] = NULL;
- }
-
- @Override
- public void bindLong(int index, long value) {
- mBindingTypes[index] = LONG;
- mLongBindings[index] = value;
- }
-
- @Override
- public void bindDouble(int index, double value) {
- mBindingTypes[index] = DOUBLE;
- mDoubleBindings[index] = value;
- }
-
- @Override
- public void bindString(int index, String value) {
- mBindingTypes[index] = STRING;
- mStringBindings[index] = value;
- }
-
- @Override
- public void bindBlob(int index, byte[] value) {
- mBindingTypes[index] = BLOB;
- mBlobBindings[index] = value;
- }
-
- @Override
- public void close() {
- // no-op. not calling release because it is internal API.
- }
-
- /**
- * Copies arguments from another RoomSQLiteQuery into this query.
- *
- * @param other The other query, which holds the arguments to be copied.
- */
- public void copyArgumentsFrom(RoomSQLiteQuery other) {
- int argCount = other.getArgCount() + 1; // +1 for the binding offsets
- System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount);
- System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount);
- System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount);
- System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount);
- System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount);
- }
-
- @Override
- public void clearBindings() {
- Arrays.fill(mBindingTypes, NULL);
- Arrays.fill(mStringBindings, null);
- Arrays.fill(mBlobBindings, null);
- mQuery = null;
- // no need to clear others
- }
-
- private static final int NULL = 1;
- private static final int LONG = 2;
- private static final int DOUBLE = 3;
- private static final int STRING = 4;
- private static final int BLOB = 5;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
- @interface Binding {
- }
-}
diff --git a/app/src/main/java/androidx/room/RoomTrackingLiveData.java b/app/src/main/java/androidx/room/RoomTrackingLiveData.java
deleted file mode 100644
index f6086584b5..0000000000
--- a/app/src/main/java/androidx/room/RoomTrackingLiveData.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2018 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 androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.lifecycle.LiveData;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData implementation that closely works with {@link InvalidationTracker} to implement
- * database drive {@link androidx.lifecycle.LiveData} queries that are strongly hold as long
- * as they are active.
- *
- * We need this extra handling for {@link androidx.lifecycle.LiveData} because when they are
- * observed forever, there is no {@link androidx.lifecycle.Lifecycle} that will keep them in
- * memory but they should stay. We cannot add-remove observer in {@link LiveData#onActive()},
- * {@link LiveData#onInactive()} because that would mean missing changes in between or doing an
- * extra query on every UI rotation.
- *
- * This {@link LiveData} keeps a weak observer to the {@link InvalidationTracker} but it is hold
- * strongly by the {@link InvalidationTracker} as long as it is active.
- */
-class RoomTrackingLiveData extends LiveData {
- @SuppressWarnings("WeakerAccess")
- final RoomDatabase mDatabase;
-
- @SuppressWarnings("WeakerAccess")
- final boolean mInTransaction;
-
- @SuppressWarnings("WeakerAccess")
- final Callable mComputeFunction;
-
- private final InvalidationLiveDataContainer mContainer;
-
- @SuppressWarnings("WeakerAccess")
- final InvalidationTracker.Observer mObserver;
-
- @SuppressWarnings("WeakerAccess")
- final AtomicBoolean mInvalid = new AtomicBoolean(true);
-
- @SuppressWarnings("WeakerAccess")
- final AtomicBoolean mComputing = new AtomicBoolean(false);
-
- @SuppressWarnings("WeakerAccess")
- final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);
-
- @SuppressWarnings("WeakerAccess")
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- if (mRegisteredObserver.compareAndSet(false, true)) {
- mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
- }
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- while (mInvalid.compareAndSet(true, false)) {
- computed = true;
- try {
- value = mComputeFunction.call();
- } catch (Exception e) {
- eu.faircode.email.Log.w(e);
- //throw new RuntimeException("Exception while computing database"
- // + " live data.", e);
- computed = false;
- }
- }
- if (computed) {
- postValue(value);
- }
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- @SuppressWarnings("WeakerAccess")
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- getQueryExecutor().execute(mRefreshRunnable);
- }
- }
- }
- };
- @SuppressLint("RestrictedApi")
- RoomTrackingLiveData(
- RoomDatabase database,
- InvalidationLiveDataContainer container,
- boolean inTransaction,
- Callable computeFunction,
- String[] tableNames) {
- mDatabase = database;
- mInTransaction = inTransaction;
- mComputeFunction = computeFunction;
- mContainer = container;
- mObserver = new InvalidationTracker.Observer(tableNames) {
- @Override
- public void onInvalidated(@NonNull Set tables) {
- ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
- }
- };
- }
-
- @Override
- protected void onActive() {
- super.onActive();
- mContainer.onActive(this);
- getQueryExecutor().execute(mRefreshRunnable);
- }
-
- @Override
- protected void onInactive() {
- super.onInactive();
- mContainer.onInactive(this);
- }
-
- Executor getQueryExecutor() {
- if (mInTransaction) {
- return mDatabase.getTransactionExecutor();
- } else {
- return mDatabase.getQueryExecutor();
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java b/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
deleted file mode 100644
index f8240ceed3..0000000000
--- a/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.room.util.CopyLock;
-import androidx.room.util.DBUtil;
-import androidx.room.util.FileUtil;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-
-/**
- * An open helper that will copy & open a pre-populated database if it doesn't exists in internal
- * storage.
- */
-class SQLiteCopyOpenHelper implements SupportSQLiteOpenHelper {
-
- @NonNull
- private final Context mContext;
- @Nullable
- private final String mCopyFromAssetPath;
- @Nullable
- private final File mCopyFromFile;
- private final int mDatabaseVersion;
- @NonNull
- private final SupportSQLiteOpenHelper mDelegate;
- @Nullable
- private DatabaseConfiguration mDatabaseConfiguration;
-
- private boolean mVerified;
-
- SQLiteCopyOpenHelper(
- @NonNull Context context,
- @Nullable String copyFromAssetPath,
- @Nullable File copyFromFile,
- int databaseVersion,
- @NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper) {
- mContext = context;
- mCopyFromAssetPath = copyFromAssetPath;
- mCopyFromFile = copyFromFile;
- mDatabaseVersion = databaseVersion;
- mDelegate = supportSQLiteOpenHelper;
- }
-
- @Override
- public String getDatabaseName() {
- return mDelegate.getDatabaseName();
- }
-
- @Override
- @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
- public void setWriteAheadLoggingEnabled(boolean enabled) {
- mDelegate.setWriteAheadLoggingEnabled(enabled);
- }
-
- @Override
- public synchronized SupportSQLiteDatabase getWritableDatabase() {
- if (!mVerified) {
- verifyDatabaseFile();
- mVerified = true;
- }
- return mDelegate.getWritableDatabase();
- }
-
- @Override
- public synchronized SupportSQLiteDatabase getReadableDatabase() {
- if (!mVerified) {
- verifyDatabaseFile();
- mVerified = true;
- }
- return mDelegate.getReadableDatabase();
- }
-
- @Override
- public synchronized void close() {
- mDelegate.close();
- mVerified = false;
- }
-
- // Can't be constructor param because the factory is needed by the database builder which in
- // turn is the one that actually builds the configuration.
- void setDatabaseConfiguration(@Nullable DatabaseConfiguration databaseConfiguration) {
- mDatabaseConfiguration = databaseConfiguration;
- }
-
- private void verifyDatabaseFile() {
- String databaseName = getDatabaseName();
- File databaseFile = mContext.getDatabasePath(databaseName);
- boolean processLevelLock = mDatabaseConfiguration == null
- || mDatabaseConfiguration.multiInstanceInvalidation;
- CopyLock copyLock = new CopyLock(databaseName, mContext.getFilesDir(), processLevelLock);
- try {
- // Acquire a copy lock, this lock works across threads and processes, preventing
- // concurrent copy attempts from occurring.
- copyLock.lock();
-
- if (!databaseFile.exists()) {
- try {
- // No database file found, copy and be done.
- copyDatabaseFile(databaseFile);
- return;
- } catch (IOException e) {
- throw new RuntimeException("Unable to copy database file.", e);
- }
- }
-
- if (mDatabaseConfiguration == null) {
- return;
- }
-
- // A database file is present, check if we need to re-copy it.
- int currentVersion;
- try {
- currentVersion = DBUtil.readVersion(databaseFile);
- } catch (IOException e) {
- Log.w(Room.LOG_TAG, "Unable to read database version.", e);
- return;
- }
-
- if (currentVersion == mDatabaseVersion) {
- return;
- }
-
- if (mDatabaseConfiguration.isMigrationRequired(currentVersion, mDatabaseVersion)) {
- // From the current version to the desired version a migration is required, i.e.
- // we won't be performing a copy destructive migration.
- return;
- }
-
- if (mContext.deleteDatabase(databaseName)) {
- try {
- copyDatabaseFile(databaseFile);
- } catch (IOException e) {
- // We are more forgiving copying a database on a destructive migration since
- // there is already a database file that can be opened.
- Log.w(Room.LOG_TAG, "Unable to copy database file.", e);
- }
- } else {
- Log.w(Room.LOG_TAG, "Failed to delete database file ("
- + databaseName + ") for a copy destructive migration.");
- }
- } finally {
- copyLock.unlock();
- }
- }
-
- private void copyDatabaseFile(File destinationFile) throws IOException {
- ReadableByteChannel input;
- if (mCopyFromAssetPath != null) {
- input = Channels.newChannel(mContext.getAssets().open(mCopyFromAssetPath));
- } else if (mCopyFromFile != null) {
- input = new FileInputStream(mCopyFromFile).getChannel();
- } else {
- throw new IllegalStateException("copyFromAssetPath and copyFromFile == null!");
- }
-
- // An intermediate file is used so that we never end up with a half-copied database file
- // in the internal directory.
- File intermediateFile = File.createTempFile(
- "room-copy-helper", ".tmp", mContext.getCacheDir());
- intermediateFile.deleteOnExit();
- FileChannel output = new FileOutputStream(intermediateFile).getChannel();
- FileUtil.copy(input, output);
-
- File parent = destinationFile.getParentFile();
- if (parent != null && !parent.exists() && !parent.mkdirs()) {
- throw new IOException("Failed to create directories for "
- + destinationFile.getAbsolutePath());
- }
-
- if (!intermediateFile.renameTo(destinationFile)) {
- throw new IOException("Failed to move intermediate file ("
- + intermediateFile.getAbsolutePath() + ") to destination ("
- + destinationFile.getAbsolutePath() + ").");
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java b/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java
deleted file mode 100644
index 22178e80ee..0000000000
--- a/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-
-import java.io.File;
-
-/**
- * Implementation of {@link SupportSQLiteOpenHelper.Factory} that creates
- * {@link SQLiteCopyOpenHelper}.
- */
-class SQLiteCopyOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
-
- @Nullable
- private final String mCopyFromAssetPath;
- @Nullable
- private final File mCopyFromFile;
- @NonNull
- private final SupportSQLiteOpenHelper.Factory mDelegate;
-
- SQLiteCopyOpenHelperFactory(
- @Nullable String copyFromAssetPath,
- @Nullable File copyFromFile,
- @NonNull SupportSQLiteOpenHelper.Factory factory) {
- mCopyFromAssetPath = copyFromAssetPath;
- mCopyFromFile = copyFromFile;
- mDelegate = factory;
- }
-
- @Override
- public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
- return new SQLiteCopyOpenHelper(
- configuration.context,
- mCopyFromAssetPath,
- mCopyFromFile,
- configuration.callback.version,
- mDelegate.create(configuration));
- }
-}
diff --git a/app/src/main/java/androidx/room/SharedSQLiteStatement.java b/app/src/main/java/androidx/room/SharedSQLiteStatement.java
deleted file mode 100644
index 20c06c8b50..0000000000
--- a/app/src/main/java/androidx/room/SharedSQLiteStatement.java
+++ /dev/null
@@ -1,100 +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 androidx.annotation.RestrictTo;
-import androidx.sqlite.db.SupportSQLiteStatement;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Represents a prepared SQLite state that can be re-used multiple times.
- *
- * This class is used by generated code. After it is used, {@code release} must be called so that
- * it can be used by other threads.
- *
- * To avoid re-entry even within the same thread, this class allows only 1 time access to the shared
- * statement until it is released.
- *
- * @hide
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class SharedSQLiteStatement {
- private final AtomicBoolean mLock = new AtomicBoolean(false);
-
- private final RoomDatabase mDatabase;
- private volatile SupportSQLiteStatement mStmt;
-
- /**
- * Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
- * it automatically creates a new one.
- *
- * @param database The database to create the statement in.
- */
- public SharedSQLiteStatement(RoomDatabase database) {
- mDatabase = database;
- }
-
- /**
- * Create the query.
- *
- * @return The SQL query to prepare.
- */
- protected abstract String createQuery();
-
- protected void assertNotMainThread() {
- mDatabase.assertNotMainThread();
- }
-
- private SupportSQLiteStatement createNewStatement() {
- String query = createQuery();
- return mDatabase.compileStatement(query);
- }
-
- private SupportSQLiteStatement getStmt(boolean canUseCached) {
- final SupportSQLiteStatement stmt;
- if (canUseCached) {
- if (mStmt == null) {
- mStmt = createNewStatement();
- }
- stmt = mStmt;
- } else {
- // it is in use, create a one off statement
- stmt = createNewStatement();
- }
- return stmt;
- }
-
- /**
- * Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done.
- */
- public SupportSQLiteStatement acquire() {
- assertNotMainThread();
- return getStmt(mLock.compareAndSet(false, true));
- }
-
- /**
- * Must call this when statement will not be used anymore.
- *
- * @param statement The statement that was returned from acquire.
- */
- public void release(SupportSQLiteStatement statement) {
- if (statement == mStmt) {
- mLock.set(false);
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/TransactionExecutor.java b/app/src/main/java/androidx/room/TransactionExecutor.java
deleted file mode 100644
index 6a6bc1260d..0000000000
--- a/app/src/main/java/androidx/room/TransactionExecutor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayDeque;
-import java.util.concurrent.Executor;
-
-/**
- * Executor wrapper for performing database transactions serially.
- *
- * Since database transactions are exclusive, this executor ensures that transactions are performed
- * in-order and one at a time, preventing threads from blocking each other when multiple concurrent
- * transactions are attempted.
- */
-class TransactionExecutor implements Executor {
-
- private final Executor mExecutor;
- private final ArrayDeque mTasks = new ArrayDeque<>();
- private Runnable mActive;
-
- TransactionExecutor(@NonNull Executor executor) {
- mExecutor = executor;
- }
-
- public synchronized void execute(final Runnable command) {
- mTasks.offer(new Runnable() {
- public void run() {
- try {
- command.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- synchronized void scheduleNext() {
- if ((mActive = mTasks.poll()) != null) {
- mExecutor.execute(mActive);
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/migration/Migration.java b/app/src/main/java/androidx/room/migration/Migration.java
deleted file mode 100644
index 4aa7a7e86a..0000000000
--- a/app/src/main/java/androidx/room/migration/Migration.java
+++ /dev/null
@@ -1,63 +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.migration;
-
-import androidx.annotation.NonNull;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-
-/**
- * Base class for a database migration.
- *
- * Each migration can move between 2 versions that are defined by {@link #startVersion} and
- * {@link #endVersion}.
- *
- * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
- * going version 3 to 5 without going to version 4). If Room opens a database at version
- * 3 and latest version is >= 5, Room will use the migration object that can migrate from
- * 3 to 5 instead of 3 to 4 and 4 to 5.
- *
- * If there are not enough migrations provided to move from the current version to the latest
- * version, Room will clear the database and recreate so even if you have no changes between 2
- * versions, you should still provide a Migration object to the builder.
- */
-public abstract class Migration {
- public final int startVersion;
- public final int endVersion;
-
- /**
- * Creates a new migration between {@code startVersion} and {@code endVersion}.
- *
- * @param startVersion The start version of the database.
- * @param endVersion The end version of the database after this migration is applied.
- */
- public Migration(int startVersion, int endVersion) {
- this.startVersion = startVersion;
- this.endVersion = endVersion;
- }
-
- /**
- * Should run the necessary migrations.
- *
- * This class cannot access any generated Dao in this method.
- *
- * This method is already called inside a transaction and that transaction might actually be a
- * composite transaction of all necessary {@code Migration}s.
- *
- * @param database The database instance
- */
- public abstract void migrate(@NonNull SupportSQLiteDatabase database);
-}
diff --git a/app/src/main/java/androidx/room/package-info.java b/app/src/main/java/androidx/room/package-info.java
deleted file mode 100644
index 3a14e974ff..0000000000
--- a/app/src/main/java/androidx/room/package-info.java
+++ /dev/null
@@ -1,129 +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.
- */
-
-/**
- * Room is a Database Object Mapping library that makes it easy to access database on Android
- * applications.
- *
- * Rather than hiding the details of SQLite, Room tries to embrace them by providing convenient APIs
- * to query the database and also verify such queries at compile time. This allows you to access
- * the full power of SQLite while having the type safety provided by Java SQL query builders.
- *
- * There are 3 major components in Room.
- *
- * - {@link androidx.room.Database Database}: This annotation marks a class as a database.
- * It should be an abstract class that extends {@link androidx.room.RoomDatabase RoomDatabase}.
- * At runtime, you can acquire an instance of it via {@link androidx.room.Room#databaseBuilder(
- * android.content.Context,java.lang.Class, java.lang.String) Room.databaseBuilder} or
- * {@link androidx.room.Room#inMemoryDatabaseBuilder(android.content.Context, java.lang.Class)
- * Room.inMemoryDatabaseBuilder}.
- *
- * The database class defines the list of entities and data access objects in the database.
- * It is also the main access point for the underlying connection.
- *
- * - {@link androidx.room.Entity Entity}: This annotation marks a class as a database row.
- * For each {@link androidx.room.Entity Entity}, a database table is created to hold the items.
- * The Entity class must be referenced in the
- * {@link androidx.room.Database#entities() Database#entities} array. Each field of the Entity
- * (and its super class) is persisted in the database unless it is denoted otherwise
- * (see {@link androidx.room.Entity Entity} docs for details).
- *
- * - {@link androidx.room.Dao Dao}: This annotation marks a class or interface as a
- * Data Access Object. Data access objects are the main components of Room that are
- * responsible for defining the methods that access the database. The class that is annotated
- * with {@link androidx.room.Database Database} must have an abstract method that has 0
- * arguments and returns the class that is annotated with Dao. While generating the code at
- * compile time, Room will generate an implementation of this class.
- *
- * Using Dao classes for database access rather than query builders or direct queries allows you
- * to keep a separation between different components and easily mock the database access while
- * testing your application.
- *
- *
- * Below is a sample of a simple database.
- *
- * // File: Song.java
- * {@literal @}Entity
- * public class User {
- * {@literal @}PrimaryKey
- * private int id;
- * private String name;
- * {@literal @}ColumnInfo(name = "release_year")
- * private int releaseYear;
- * // getters and setters are ignored for brevity but they are required for Room to work.
- * }
- * // File: SongDao.java
- * {@literal @}Dao
- * public interface SongDao {
- * {@literal @}Query("SELECT * FROM song")
- * List<Song> loadAll();
- * {@literal @}Query("SELECT * FROM song WHERE id IN (:songIds)")
- * List<Song> loadAllBySongId(int... songIds);
- * {@literal @}Query("SELECT * FROM song WHERE name LIKE :name AND release_year = :year LIMIT 1")
- * Song loadOneByNameAndReleaseYear(String first, int year);
- * {@literal @}Insert
- * void insertAll(Song... songs);
- * {@literal @}Delete
- * void delete(Song song);
- * }
- * // File: MusicDatabase.java
- * {@literal @}Database(entities = {Song.java})
- * public abstract class MusicDatabase extends RoomDatabase {
- * public abstract SongDao userDao();
- * }
- *
- * You can create an instance of {@code MusicDatabase} as follows:
- *
- * MusicDatabase db = Room
- * .databaseBuilder(getApplicationContext(), MusicDatabase.class, "database-name")
- * .build();
- *
- * Since Room verifies your queries at compile time, it also detects information about which tables
- * are accessed by the query or what columns are present in the response.
- *
- * You can observe a particular table for changes using the
- * {@link androidx.room.InvalidationTracker InvalidationTracker} class which you can acquire via
- * {@link androidx.room.RoomDatabase#getInvalidationTracker()
- * RoomDatabase.getInvalidationTracker}.
- *
- * For convenience, Room allows you to return {@link androidx.lifecycle.LiveData LiveData} from
- * {@link androidx.room.Query Query} methods. It will automatically observe the related tables as
- * long as the {@code LiveData} has active observers.
- *
- * // This live data will automatically dispatch changes as the database changes.
- * {@literal @}Query("SELECT * FROM song ORDER BY name LIMIT 5")
- * LiveData<Song> loadFirstFiveSongs();
- *
- *
- * You can also return arbitrary data objects from your query results as long as the fields in the
- * object match the list of columns in the query response. This makes it very easy to write
- * applications that drive the UI from persistent storage.
- *
- * class IdAndSongHeader {
- * int id;
- * {@literal @}ColumnInfo(name = "header")
- * String header;
- * }
- * // DAO
- * {@literal @}Query("SELECT id, name || '-' || release_year AS header FROM user")
- * public IdAndSongHeader[] loadSongHeaders();
- *
- * If there is a mismatch between the query result and the POJO, Room will print a warning during
- * compilation.
- *
- * Please see the documentation of individual classes for details.
- */
-package androidx.room;
diff --git a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java b/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
deleted file mode 100644
index 156ee24ac7..0000000000
--- a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
+++ /dev/null
@@ -1,195 +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.paging;
-
-import android.database.Cursor;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.paging.PositionalDataSource;
-import androidx.room.InvalidationTracker;
-import androidx.room.RoomDatabase;
-import androidx.room.RoomSQLiteQuery;
-import androidx.sqlite.db.SupportSQLiteQuery;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A simple data source implementation that uses Limit & Offset to page the query.
- *
- * This is NOT the most efficient way to do paging on SQLite. It is
- * recommended to use an indexed
- * ORDER BY statement but that requires a more complex API. This solution is technically equal to
- * receiving a {@link Cursor} from a large query but avoids the need to manually manage it, and
- * never returns inconsistent data if it is invalidated.
- *
- * @param Data type returned by the data source.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class LimitOffsetDataSource extends PositionalDataSource {
- private final RoomSQLiteQuery mSourceQuery;
- private final String mCountQuery;
- private final String mLimitOffsetQuery;
- private final RoomDatabase mDb;
- @SuppressWarnings("FieldCanBeLocal")
- private final InvalidationTracker.Observer mObserver;
- private final boolean mInTransaction;
-
- protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
- boolean inTransaction, String... tables) {
- this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
- }
-
- protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
- boolean inTransaction, String... tables) {
- mDb = db;
- mSourceQuery = query;
- mInTransaction = inTransaction;
- mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
- mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
- mObserver = new InvalidationTracker.Observer(tables) {
- @Override
- public void onInvalidated(@NonNull Set tables) {
- invalidate();
- }
- };
- db.getInvalidationTracker().addWeakObserver(mObserver);
- }
-
- /**
- * Count number of rows query can return
- *
- * @hide
- */
- @SuppressWarnings("WeakerAccess")
- public int countItems() {
- final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery,
- mSourceQuery.getArgCount());
- sqLiteQuery.copyArgumentsFrom(mSourceQuery);
- Cursor cursor = mDb.query(sqLiteQuery);
- try {
- if (cursor.moveToFirst()) {
- return cursor.getInt(0);
- }
- return 0;
- } finally {
- cursor.close();
- sqLiteQuery.release();
- }
- }
-
- @Override
- public boolean isInvalid() {
- mDb.getInvalidationTracker().refreshVersionsSync();
- return super.isInvalid();
- }
-
- @SuppressWarnings("WeakerAccess")
- protected abstract List convertRows(Cursor cursor);
-
- @SuppressWarnings("deprecation")
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback callback) {
- List list = Collections.emptyList();
- int totalCount = 0;
- int firstLoadPosition = 0;
- RoomSQLiteQuery sqLiteQuery = null;
- Cursor cursor = null;
- mDb.beginTransaction();
- try {
- totalCount = countItems();
- if (totalCount != 0) {
- // bound the size requested, based on known count
- firstLoadPosition = computeInitialLoadPosition(params, totalCount);
- int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
-
- sqLiteQuery = getSQLiteQuery(firstLoadPosition, firstLoadSize);
- cursor = mDb.query(sqLiteQuery);
- List rows = convertRows(cursor);
- mDb.setTransactionSuccessful();
- list = rows;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- mDb.endTransaction();
- if (sqLiteQuery != null) {
- sqLiteQuery.release();
- }
- }
-
- callback.onResult(list, firstLoadPosition, totalCount);
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback callback) {
- callback.onResult(loadRange(params.startPosition, params.loadSize));
- }
-
- /**
- * Return the rows from startPos to startPos + loadCount
- *
- * @hide
- */
- @SuppressWarnings("deprecation")
- @NonNull
- public List loadRange(int startPosition, int loadCount) {
- final RoomSQLiteQuery sqLiteQuery = getSQLiteQuery(startPosition, loadCount);
- if (mInTransaction) {
- mDb.beginTransaction();
- Cursor cursor = null;
- //noinspection TryFinallyCanBeTryWithResources
- try {
- cursor = mDb.query(sqLiteQuery);
- List rows = convertRows(cursor);
- mDb.setTransactionSuccessful();
- return rows;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- mDb.endTransaction();
- sqLiteQuery.release();
- }
- } else {
- Cursor cursor = mDb.query(sqLiteQuery);
- //noinspection TryFinallyCanBeTryWithResources
- try {
- return convertRows(cursor);
- } finally {
- cursor.close();
- sqLiteQuery.release();
- }
- }
- }
-
- private RoomSQLiteQuery getSQLiteQuery(int startPosition, int loadCount) {
- final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery,
- mSourceQuery.getArgCount() + 2);
- sqLiteQuery.copyArgumentsFrom(mSourceQuery);
- sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
- sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
- return sqLiteQuery;
- }
-}
diff --git a/app/src/main/java/androidx/room/util/CopyLock.java b/app/src/main/java/androidx/room/util/CopyLock.java
deleted file mode 100644
index 3750315f34..0000000000
--- a/app/src/main/java/androidx/room/util/CopyLock.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.util;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Utility class for in-process and multi-process key-based lock mechanism for safely copying
- * database files.
- *
- * Acquiring the lock will be quick if no other thread or process has a lock with the same key.
- * But if the lock is already held then acquiring it will block, until the other thread or process
- * releases the lock. Note that the key and lock directory must be the same to achieve
- * synchronization.
- *
- * Locking is done via two levels:
- *
- * -
- * Thread locking within the same JVM process is done via a map of String key to ReentrantLock
- * objects.
- *
-
- * Multi-process locking is done via a dummy file whose name contains the key and FileLock
- * objects.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class CopyLock {
-
- // in-process lock map
- private static final Map sThreadLocks = new HashMap<>();
-
- private final File mCopyLockFile;
- private final Lock mThreadLock;
- private final boolean mFileLevelLock;
- private FileChannel mLockChannel;
-
- /**
- * Creates a lock with {@code name} and using {@code lockDir} as the directory for the
- * lock files.
- * @param name the name of this lock.
- * @param lockDir the directory where the lock files will be located.
- * @param processLock whether to use file for process level locking or not.
- */
- public CopyLock(@NonNull String name, @NonNull File lockDir, boolean processLock) {
- mCopyLockFile = new File(lockDir, name + ".lck");
- mThreadLock = getThreadLock(mCopyLockFile.getAbsolutePath());
- mFileLevelLock = processLock;
- }
-
- /**
- * Attempts to grab the lock, blocking if already held by another thread or process.
- */
- public void lock() {
- mThreadLock.lock();
- if (mFileLevelLock) {
- try {
- mLockChannel = new FileOutputStream(mCopyLockFile).getChannel();
- mLockChannel.lock();
- } catch (IOException e) {
- throw new IllegalStateException("Unable to grab copy lock.", e);
- }
- }
- }
-
- /**
- * Releases the lock.
- */
- public void unlock() {
- if (mLockChannel != null) {
- try {
- mLockChannel.close();
- } catch (IOException ignored) { }
- }
- mThreadLock.unlock();
- }
-
- private static Lock getThreadLock(String key) {
- synchronized (sThreadLocks) {
- Lock threadLock = sThreadLocks.get(key);
- if (threadLock == null) {
- threadLock = new ReentrantLock();
- sThreadLocks.put(key, threadLock);
- }
- return threadLock;
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/util/CursorUtil.java b/app/src/main/java/androidx/room/util/CursorUtil.java
deleted file mode 100644
index 05b8c59170..0000000000
--- a/app/src/main/java/androidx/room/util/CursorUtil.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 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.util;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Cursor utilities for Room
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class CursorUtil {
-
- /**
- * Copies the given cursor into a in-memory cursor and then closes it.
- *
- * This is useful for iterating over a cursor multiple times without the cost of JNI while
- * reading or IO while filling the window at the expense of memory consumption.
- *
- * @param c the cursor to copy.
- * @return a new cursor containing the same data as the given cursor.
- */
- @NonNull
- public static Cursor copyAndClose(@NonNull Cursor c) {
- final MatrixCursor matrixCursor;
- try {
- matrixCursor = new MatrixCursor(c.getColumnNames(), c.getCount());
- while (c.moveToNext()) {
- final Object[] row = new Object[c.getColumnCount()];
- for (int i = 0; i < c.getColumnCount(); i++) {
- switch (c.getType(i)) {
- case Cursor.FIELD_TYPE_NULL:
- row[i] = null;
- break;
- case Cursor.FIELD_TYPE_INTEGER:
- row[i] = c.getLong(i);
- break;
- case Cursor.FIELD_TYPE_FLOAT:
- row[i] = c.getDouble(i);
- break;
- case Cursor.FIELD_TYPE_STRING:
- row[i] = c.getString(i);
- break;
- case Cursor.FIELD_TYPE_BLOB:
- row[i] = c.getBlob(i);
- break;
- default:
- throw new IllegalStateException();
- }
- }
- matrixCursor.addRow(row);
- }
- } finally {
- c.close();
- }
- return matrixCursor;
- }
-
- /**
- * Patches {@link Cursor#getColumnIndex(String)} to work around issues on older devices.
- * If the column is not found, it retries with the specified name surrounded by backticks.
- *
- * @param c The cursor.
- * @param name The name of the target column.
- * @return The index of the column, or -1 if not found.
- */
- public static int getColumnIndex(@NonNull Cursor c, @NonNull String name) {
- final int index = c.getColumnIndex(name);
- if (index >= 0) {
- return index;
- }
- return c.getColumnIndex("`" + name + "`");
- }
-
- /**
- * Patches {@link Cursor#getColumnIndexOrThrow(String)} to work around issues on older devices.
- * If the column is not found, it retries with the specified name surrounded by backticks.
- *
- * @param c The cursor.
- * @param name The name of the target column.
- * @return The index of the column.
- * @throws IllegalArgumentException if the column does not exist.
- */
- public static int getColumnIndexOrThrow(@NonNull Cursor c, @NonNull String name) {
- final int index = c.getColumnIndex(name);
- if (index >= 0) {
- return index;
- }
- return c.getColumnIndexOrThrow("`" + name + "`");
- }
-
- private CursorUtil() {
- }
-}
diff --git a/app/src/main/java/androidx/room/util/DBUtil.java b/app/src/main/java/androidx/room/util/DBUtil.java
deleted file mode 100644
index f7afd265a3..0000000000
--- a/app/src/main/java/androidx/room/util/DBUtil.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2018 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.util;
-
-import android.database.AbstractWindowedCursor;
-import android.database.Cursor;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.room.RoomDatabase;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteQuery;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Database utilities for Room
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class DBUtil {
-
- /**
- * Performs the SQLiteQuery on the given database.
- *
- * This util method encapsulates copying the cursor if the {@code maybeCopy} parameter is
- * {@code true} and either the api level is below a certain threshold or the full result of the
- * query does not fit in a single window.
- *
- * @param db The database to perform the query on.
- * @param sqLiteQuery The query to perform.
- * @param maybeCopy True if the result cursor should maybe be copied, false otherwise.
- * @return Result of the query.
- */
- @NonNull
- public static Cursor query(RoomDatabase db, SupportSQLiteQuery sqLiteQuery, boolean maybeCopy) {
- final Cursor cursor = db.query(sqLiteQuery);
- if (maybeCopy && cursor instanceof AbstractWindowedCursor) {
- AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
- int rowsInCursor = windowedCursor.getCount(); // Should fill the window.
- int rowsInWindow;
- if (windowedCursor.hasWindow()) {
- rowsInWindow = windowedCursor.getWindow().getNumRows();
- } else {
- rowsInWindow = rowsInCursor;
- }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || rowsInWindow < rowsInCursor) {
- return CursorUtil.copyAndClose(windowedCursor);
- }
- }
-
- return cursor;
- }
-
- /**
- * Drops all FTS content sync triggers created by Room.
- *
- * FTS content sync triggers created by Room are those that are found in the sqlite_master table
- * who's names start with 'room_fts_content_sync_'.
- *
- * @param db The database.
- */
- public static void dropFtsSyncTriggers(SupportSQLiteDatabase db) {
- List existingTriggers = new ArrayList<>();
- Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type = 'trigger'");
- //noinspection TryFinallyCanBeTryWithResources
- try {
- while (cursor.moveToNext()) {
- existingTriggers.add(cursor.getString(0));
- }
- } finally {
- cursor.close();
- }
-
- for (String triggerName : existingTriggers) {
- if (triggerName.startsWith("room_fts_content_sync_")) {
- db.execSQL("DROP TRIGGER IF EXISTS " + triggerName);
- }
- }
- }
-
- /**
- * Reads the user version number out of the database header from the given file.
- *
- * @param databaseFile the database file.
- * @return the database version
- * @throws IOException if something goes wrong reading the file, such as bad database header or
- * missing permissions.
- *
- * @see User Version
- * Number.
- */
- public static int readVersion(@NonNull File databaseFile) throws IOException {
- FileChannel input = null;
- try {
- ByteBuffer buffer = ByteBuffer.allocate(4);
- input = new FileInputStream(databaseFile).getChannel();
- input.tryLock(60, 4, true);
- input.position(60);
- int read = input.read(buffer);
- if (read != 4) {
- throw new IOException("Bad database header, unable to read 4 bytes at offset 60");
- }
- buffer.rewind();
- return buffer.getInt(); // ByteBuffer is big-endian by default
- } finally {
- if (input != null) {
- input.close();
- }
- }
- }
-
- private DBUtil() {
- }
-}
diff --git a/app/src/main/java/androidx/room/util/FileUtil.java b/app/src/main/java/androidx/room/util/FileUtil.java
deleted file mode 100644
index a221ff741a..0000000000
--- a/app/src/main/java/androidx/room/util/FileUtil.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.util;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-
-/**
- * File utilities for Room
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class FileUtil {
-
- /**
- * Copies data from the input channel to the output file channel.
- *
- * @param input the input channel to copy.
- * @param output the output channel to copy.
- * @throws IOException if there is an I/O error.
- */
- @SuppressLint("LambdaLast")
- public static void copy(@NonNull ReadableByteChannel input, @NonNull FileChannel output)
- throws IOException {
- try {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
- output.transferFrom(input, 0, Long.MAX_VALUE);
- } else {
- InputStream inputStream = Channels.newInputStream(input);
- OutputStream outputStream = Channels.newOutputStream(output);
- int length;
- byte[] buffer = new byte[1024 * 4];
- while ((length = inputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, length);
- }
- }
- output.force(false);
- } finally {
- input.close();
- output.close();
- }
- }
-
- private FileUtil() {
- }
-}
diff --git a/app/src/main/java/androidx/room/util/FtsTableInfo.java b/app/src/main/java/androidx/room/util/FtsTableInfo.java
deleted file mode 100644
index c076a0238b..0000000000
--- a/app/src/main/java/androidx/room/util/FtsTableInfo.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright 2018 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.util;
-
-import android.database.Cursor;
-
-import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A data class that holds the information about an FTS table.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class FtsTableInfo {
-
- // A set of valid FTS Options
- private static final String[] FTS_OPTIONS = new String[] {
- "tokenize=", "compress=", "content=", "languageid=", "matchinfo=", "notindexed=",
- "order=", "prefix=", "uncompress="};
-
- /**
- * The table name
- */
- public final String name;
-
- /**
- * The column names
- */
- public final Set columns;
-
- /**
- * The set of options. Each value in the set contains the option in the following format:
- * <key>=<value>.
- */
- public final Set options;
-
- public FtsTableInfo(String name, Set columns, Set options) {
- this.name = name;
- this.columns = columns;
- this.options = options;
- }
-
- public FtsTableInfo(String name, Set columns, String createSql) {
- this.name = name;
- this.columns = columns;
- this.options = parseOptions(createSql);
- }
-
- /**
- * Reads the table information from the given database.
- *
- * @param database The database to read the information from.
- * @param tableName The table name.
- * @return A FtsTableInfo containing the columns and options for the provided table name.
- */
- public static FtsTableInfo read(SupportSQLiteDatabase database, String tableName) {
- Set columns = readColumns(database, tableName);
- Set options = readOptions(database, tableName);
-
- return new FtsTableInfo(tableName, columns, options);
- }
-
- @SuppressWarnings("TryFinallyCanBeTryWithResources")
- private static Set readColumns(SupportSQLiteDatabase database, String tableName) {
- Cursor cursor = database.query("PRAGMA table_info(`" + tableName + "`)");
- Set columns = new HashSet<>();
- try {
- if (cursor.getColumnCount() > 0) {
- int nameIndex = cursor.getColumnIndex("name");
- while (cursor.moveToNext()) {
- columns.add(cursor.getString(nameIndex));
- }
- }
- } finally {
- cursor.close();
- }
- return columns;
- }
-
- @SuppressWarnings("TryFinallyCanBeTryWithResources")
- private static Set readOptions(SupportSQLiteDatabase database, String tableName) {
- String sql = "";
- Cursor cursor = database.query(
- "SELECT * FROM sqlite_master WHERE `name` = '" + tableName + "'");
- try {
- if (cursor.moveToFirst()) {
- sql = cursor.getString(cursor.getColumnIndexOrThrow("sql"));
- }
- } finally {
- cursor.close();
- }
- return parseOptions(sql);
- }
-
- /**
- * Parses FTS options from the create statement of an FTS table.
- *
- * This method assumes the given create statement is a valid well-formed SQLite statement as
- * defined in the CREATE VIRTUAL TABLE
- * syntax diagram.
- *
- * @param createStatement the "CREATE VIRTUAL TABLE" statement.
- * @return the set of FTS option key and values in the create statement.
- */
- @VisibleForTesting
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static Set parseOptions(String createStatement) {
- if (createStatement.isEmpty()) {
- return new HashSet<>();
- }
-
- // Module arguments are within the parenthesis followed by the module name.
- String argsString = createStatement.substring(
- createStatement.indexOf('(') + 1,
- createStatement.lastIndexOf(')'));
-
- // Split the module argument string by the comma delimiter, keeping track of quotation so
- // so that if the delimiter is found within a string literal we don't substring at the wrong
- // index. SQLite supports four ways of quoting keywords, see:
- // https://www.sqlite.org/lang_keywords.html
- List args = new ArrayList<>();
- ArrayDeque quoteStack = new ArrayDeque<>();
- int lastDelimiterIndex = -1;
- for (int i = 0; i < argsString.length(); i++) {
- char c = argsString.charAt(i);
- switch (c) {
- case '\'':
- case '"':
- case '`':
- if (quoteStack.isEmpty()) {
- quoteStack.push(c);
- } else if (quoteStack.peek() == c) {
- quoteStack.pop();
- }
- break;
- case '[':
- if (quoteStack.isEmpty()) {
- quoteStack.push(c);
- }
- break;
- case ']':
- if (!quoteStack.isEmpty() && quoteStack.peek() == '[') {
- quoteStack.pop();
- }
- break;
- case ',':
- if (quoteStack.isEmpty()) {
- args.add(argsString.substring(lastDelimiterIndex + 1, i).trim());
- lastDelimiterIndex = i;
- }
- break;
- }
- }
- args.add(argsString.substring(lastDelimiterIndex + 1).trim()); // Add final argument.
-
- // Match args against valid options, otherwise they are column definitions.
- HashSet options = new HashSet<>();
- for (String arg : args) {
- for (String validOption : FTS_OPTIONS) {
- if (arg.startsWith(validOption)) {
- options.add(arg);
- }
- }
- }
-
- return options;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- FtsTableInfo that = (FtsTableInfo) o;
-
- if (name != null ? !name.equals(that.name) : that.name != null) return false;
- if (columns != null ? !columns.equals(that.columns) : that.columns != null) return false;
- return options != null ? options.equals(that.options) : that.options == null;
- }
-
- @Override
- public int hashCode() {
- int result = name != null ? name.hashCode() : 0;
- result = 31 * result + (columns != null ? columns.hashCode() : 0);
- result = 31 * result + (options != null ? options.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "FtsTableInfo{"
- + "name='" + name + '\''
- + ", columns=" + columns
- + ", options=" + options
- + '}';
- }
-}
diff --git a/app/src/main/java/androidx/room/util/SneakyThrow.java b/app/src/main/java/androidx/room/util/SneakyThrow.java
deleted file mode 100644
index 76968ab344..0000000000
--- a/app/src/main/java/androidx/room/util/SneakyThrow.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.util;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Java 8 Sneaky Throw technique.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class SneakyThrow {
-
- /**
- * Re-throws a checked exception as if it was a runtime exception without wrapping it.
- *
- * @param e the exception to re-throw.
- */
- public static void reThrow(@NonNull Exception e) {
- sneakyThrow(e);
- }
-
- @SuppressWarnings("unchecked")
- private static void sneakyThrow(@NonNull Throwable e) throws E {
- throw (E) e;
- }
-
- private SneakyThrow() {
-
- }
-}
diff --git a/app/src/main/java/androidx/room/util/StringUtil.java b/app/src/main/java/androidx/room/util/StringUtil.java
deleted file mode 100644
index 2b30eb64de..0000000000
--- a/app/src/main/java/androidx/room/util/StringUtil.java
+++ /dev/null
@@ -1,118 +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.util;
-
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/**
- * @hide
- *
- * String utilities for Room
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class StringUtil {
-
- @SuppressWarnings("unused")
- public static final String[] EMPTY_STRING_ARRAY = new String[0];
- /**
- * Returns a new StringBuilder to be used while producing SQL queries.
- *
- * @return A new or recycled StringBuilder
- */
- public static StringBuilder newStringBuilder() {
- // TODO pool:
- return new StringBuilder();
- }
-
- /**
- * Adds bind variable placeholders (?) to the given string. Each placeholder is separated
- * by a comma.
- *
- * @param builder The StringBuilder for the query
- * @param count Number of placeholders
- */
- public static void appendPlaceholders(StringBuilder builder, int count) {
- for (int i = 0; i < count; i++) {
- builder.append("?");
- if (i < count - 1) {
- builder.append(",");
- }
- }
- }
- /**
- * Splits a comma separated list of integers to integer list.
- *
- * If an input is malformed, it is omitted from the result.
- *
- * @param input Comma separated list of integers.
- * @return A List containing the integers or null if the input is null.
- */
- @Nullable
- public static List splitToIntList(@Nullable String input) {
- if (input == null) {
- return null;
- }
- List result = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(input, ",");
- while (tokenizer.hasMoreElements()) {
- final String item = tokenizer.nextToken();
- try {
- result.add(Integer.parseInt(item));
- } catch (NumberFormatException ex) {
- Log.e("ROOM", "Malformed integer list", ex);
- }
- }
- return result;
- }
-
- /**
- * Joins the given list of integers into a comma separated list.
- *
- * @param input The list of integers.
- * @return Comma separated string composed of integers in the list. If the list is null, return
- * value is null.
- */
- @Nullable
- public static String joinIntoString(@Nullable List input) {
- if (input == null) {
- return null;
- }
-
- final int size = input.size();
- if (size == 0) {
- return "";
- }
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < size; i++) {
- sb.append(Integer.toString(input.get(i)));
- if (i < size - 1) {
- sb.append(",");
- }
- }
- return sb.toString();
- }
-
- private StringUtil() {
- }
-}
diff --git a/app/src/main/java/androidx/room/util/TableInfo.java b/app/src/main/java/androidx/room/util/TableInfo.java
deleted file mode 100644
index a28aea49d3..0000000000
--- a/app/src/main/java/androidx/room/util/TableInfo.java
+++ /dev/null
@@ -1,665 +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.util;
-
-import android.database.Cursor;
-import android.os.Build;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.room.ColumnInfo;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * A data class that holds the information about a table.
- *
- * It directly maps to the result of {@code PRAGMA table_info()}. Check the
- * PRAGMA table_info
- * documentation for more details.
- *
- * Even though SQLite column names are case insensitive, this class uses case sensitive matching.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources",
- "SimplifiableIfStatement"})
-// if you change this class, you must change TableInfoWriter.kt
-public class TableInfo {
-
- /**
- * Identifies from where the info object was created.
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {CREATED_FROM_UNKNOWN, CREATED_FROM_ENTITY, CREATED_FROM_DATABASE})
- @interface CreatedFrom {
- }
-
- /**
- * Identifier for when the info is created from an unknown source.
- */
- public static final int CREATED_FROM_UNKNOWN = 0;
-
- /**
- * Identifier for when the info is created from an entity definition, such as generated code
- * by the compiler or at runtime from a schema bundle, parsed from a schema JSON file.
- */
- public static final int CREATED_FROM_ENTITY = 1;
-
- /**
- * Identifier for when the info is created from the database itself, reading information from a
- * PRAGMA, such as table_info.
- */
- public static final int CREATED_FROM_DATABASE = 2;
-
- /**
- * The table name.
- */
- public final String name;
- /**
- * Unmodifiable map of columns keyed by column name.
- */
- public final Map columns;
-
- public final Set foreignKeys;
-
- /**
- * Sometimes, Index information is not available (older versions). If so, we skip their
- * verification.
- */
- @Nullable
- public final Set indices;
-
- @SuppressWarnings("unused")
- public TableInfo(String name, Map columns, Set foreignKeys,
- Set indices) {
- this.name = name;
- this.columns = Collections.unmodifiableMap(columns);
- this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
- this.indices = indices == null ? null : Collections.unmodifiableSet(indices);
- }
-
- /**
- * For backward compatibility with dbs created with older versions.
- */
- @SuppressWarnings("unused")
- public TableInfo(String name, Map columns, Set foreignKeys) {
- this(name, columns, foreignKeys, Collections.emptySet());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- TableInfo tableInfo = (TableInfo) o;
-
- if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false;
- if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) {
- return false;
- }
- if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys)
- : tableInfo.foreignKeys != null) {
- return false;
- }
- if (indices == null || tableInfo.indices == null) {
- // if one us is missing index information, seems like we couldn't acquire the
- // information so we better skip.
- return true;
- }
- return indices.equals(tableInfo.indices);
- }
-
- @Override
- public int hashCode() {
- int result = name != null ? name.hashCode() : 0;
- result = 31 * result + (columns != null ? columns.hashCode() : 0);
- result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0);
- // skip index, it is not reliable for comparison.
- return result;
- }
-
- @Override
- public String toString() {
- return "TableInfo{"
- + "name='" + name + '\''
- + ", columns=" + columns
- + ", foreignKeys=" + foreignKeys
- + ", indices=" + indices
- + '}';
- }
-
- /**
- * Reads the table information from the given database.
- *
- * @param database The database to read the information from.
- * @param tableName The table name.
- * @return A TableInfo containing the schema information for the provided table name.
- */
- @SuppressWarnings("SameParameterValue")
- public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
- Map columns = readColumns(database, tableName);
- Set foreignKeys = readForeignKeys(database, tableName);
- Set indices = readIndices(database, tableName);
- return new TableInfo(tableName, columns, foreignKeys, indices);
- }
-
- private static Set readForeignKeys(SupportSQLiteDatabase database,
- String tableName) {
- Set foreignKeys = new HashSet<>();
- // this seems to return everything in order but it is not documented so better be safe
- Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)");
- try {
- final int idColumnIndex = cursor.getColumnIndex("id");
- final int seqColumnIndex = cursor.getColumnIndex("seq");
- final int tableColumnIndex = cursor.getColumnIndex("table");
- final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete");
- final int onUpdateColumnIndex = cursor.getColumnIndex("on_update");
-
- final List ordered = readForeignKeyFieldMappings(cursor);
- final int count = cursor.getCount();
- for (int position = 0; position < count; position++) {
- cursor.moveToPosition(position);
- final int seq = cursor.getInt(seqColumnIndex);
- if (seq != 0) {
- continue;
- }
- final int id = cursor.getInt(idColumnIndex);
- List myColumns = new ArrayList<>();
- List refColumns = new ArrayList<>();
- for (ForeignKeyWithSequence key : ordered) {
- if (key.mId == id) {
- myColumns.add(key.mFrom);
- refColumns.add(key.mTo);
- }
- }
- foreignKeys.add(new ForeignKey(
- cursor.getString(tableColumnIndex),
- cursor.getString(onDeleteColumnIndex),
- cursor.getString(onUpdateColumnIndex),
- myColumns,
- refColumns
- ));
- }
- } finally {
- cursor.close();
- }
- return foreignKeys;
- }
-
- private static List readForeignKeyFieldMappings(Cursor cursor) {
- final int idColumnIndex = cursor.getColumnIndex("id");
- final int seqColumnIndex = cursor.getColumnIndex("seq");
- final int fromColumnIndex = cursor.getColumnIndex("from");
- final int toColumnIndex = cursor.getColumnIndex("to");
- final int count = cursor.getCount();
- List result = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- cursor.moveToPosition(i);
- result.add(new ForeignKeyWithSequence(
- cursor.getInt(idColumnIndex),
- cursor.getInt(seqColumnIndex),
- cursor.getString(fromColumnIndex),
- cursor.getString(toColumnIndex)
- ));
- }
- Collections.sort(result);
- return result;
- }
-
- private static Map readColumns(SupportSQLiteDatabase database,
- String tableName) {
- Cursor cursor = database
- .query("PRAGMA table_info(`" + tableName + "`)");
- //noinspection TryFinallyCanBeTryWithResources
- Map columns = new HashMap<>();
- try {
- if (cursor.getColumnCount() > 0) {
- int nameIndex = cursor.getColumnIndex("name");
- int typeIndex = cursor.getColumnIndex("type");
- int notNullIndex = cursor.getColumnIndex("notnull");
- int pkIndex = cursor.getColumnIndex("pk");
- int defaultValueIndex = cursor.getColumnIndex("dflt_value");
-
- while (cursor.moveToNext()) {
- final String name = cursor.getString(nameIndex);
- final String type = cursor.getString(typeIndex);
- final boolean notNull = 0 != cursor.getInt(notNullIndex);
- final int primaryKeyPosition = cursor.getInt(pkIndex);
- final String defaultValue = cursor.getString(defaultValueIndex);
- columns.put(name,
- new Column(name, type, notNull, primaryKeyPosition, defaultValue,
- CREATED_FROM_DATABASE));
- }
- }
- } finally {
- cursor.close();
- }
- return columns;
- }
-
- /**
- * @return null if we cannot read the indices due to older sqlite implementations.
- */
- @Nullable
- private static Set readIndices(SupportSQLiteDatabase database, String tableName) {
- Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)");
- try {
- final int nameColumnIndex = cursor.getColumnIndex("name");
- final int originColumnIndex = cursor.getColumnIndex("origin");
- final int uniqueIndex = cursor.getColumnIndex("unique");
- if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
- // we cannot read them so better not validate any index.
- return null;
- }
- HashSet indices = new HashSet<>();
- while (cursor.moveToNext()) {
- String origin = cursor.getString(originColumnIndex);
- if (!"c".equals(origin)) {
- // Ignore auto-created indices
- continue;
- }
- String name = cursor.getString(nameColumnIndex);
- boolean unique = cursor.getInt(uniqueIndex) == 1;
- Index index = readIndex(database, name, unique);
- if (index == null) {
- // we cannot read it properly so better not read it
- return null;
- }
- indices.add(index);
- }
- return indices;
- } finally {
- cursor.close();
- }
- }
-
- /**
- * @return null if we cannot read the index due to older sqlite implementations.
- */
- @Nullable
- private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) {
- Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)");
- try {
- final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
- final int cidColumnIndex = cursor.getColumnIndex("cid");
- final int nameColumnIndex = cursor.getColumnIndex("name");
- if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) {
- // we cannot read them so better not validate any index.
- return null;
- }
- final TreeMap results = new TreeMap<>();
-
- while (cursor.moveToNext()) {
- int cid = cursor.getInt(cidColumnIndex);
- if (cid < 0) {
- // Ignore SQLite row ID
- continue;
- }
- int seq = cursor.getInt(seqnoColumnIndex);
- String columnName = cursor.getString(nameColumnIndex);
- results.put(seq, columnName);
- }
- final List columns = new ArrayList<>(results.size());
- columns.addAll(results.values());
- return new Index(name, unique, columns);
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Holds the information about a database column.
- */
- @SuppressWarnings("WeakerAccess")
- public static class Column {
- /**
- * The column name.
- */
- public final String name;
- /**
- * The column type affinity.
- */
- public final String type;
- /**
- * The column type after it is normalized to one of the basic types according to
- * https://www.sqlite.org/datatype3.html Section 3.1.
- *
- * This is the value Room uses for equality check.
- */
- @ColumnInfo.SQLiteTypeAffinity
- public final int affinity;
- /**
- * Whether or not the column can be NULL.
- */
- public final boolean notNull;
- /**
- * The position of the column in the list of primary keys, 0 if the column is not part
- * of the primary key.
- *
- * This information is only available in API 20+.
- * (SQLite version 3.7.16.2)
- * On older platforms, it will be 1 if the column is part of the primary key and 0
- * otherwise.
- *
- * The {@link #equals(Object)} implementation handles this inconsistency based on
- * API levels os if you are using a custom SQLite deployment, it may return false
- * positives.
- */
- public final int primaryKeyPosition;
- /**
- * The default value of this column.
- */
- public final String defaultValue;
-
- @CreatedFrom
- private final int mCreatedFrom;
-
- /**
- * @deprecated Use {@link Column#Column(String, String, boolean, int, String, int)} instead.
- */
- @Deprecated
- public Column(String name, String type, boolean notNull, int primaryKeyPosition) {
- this(name, type, notNull, primaryKeyPosition, null, CREATED_FROM_UNKNOWN);
- }
-
- // if you change this constructor, you must change TableInfoWriter.kt
- public Column(String name, String type, boolean notNull, int primaryKeyPosition,
- String defaultValue, @CreatedFrom int createdFrom) {
- this.name = name;
- this.type = type;
- this.notNull = notNull;
- this.primaryKeyPosition = primaryKeyPosition;
- this.affinity = findAffinity(type);
- this.defaultValue = defaultValue;
- this.mCreatedFrom = createdFrom;
- }
-
- /**
- * Implements https://www.sqlite.org/datatype3.html section 3.1
- *
- * @param type The type that was given to the sqlite
- * @return The normalized type which is one of the 5 known affinities
- */
- @ColumnInfo.SQLiteTypeAffinity
- private static int findAffinity(@Nullable String type) {
- if (type == null) {
- return ColumnInfo.BLOB;
- }
- String uppercaseType = type.toUpperCase(Locale.US);
- if (uppercaseType.contains("INT")) {
- return ColumnInfo.INTEGER;
- }
- if (uppercaseType.contains("CHAR")
- || uppercaseType.contains("CLOB")
- || uppercaseType.contains("TEXT")) {
- return ColumnInfo.TEXT;
- }
- if (uppercaseType.contains("BLOB")) {
- return ColumnInfo.BLOB;
- }
- if (uppercaseType.contains("REAL")
- || uppercaseType.contains("FLOA")
- || uppercaseType.contains("DOUB")) {
- return ColumnInfo.REAL;
- }
- // sqlite returns NUMERIC here but it is like a catch all. We already
- // have UNDEFINED so it is better to use UNDEFINED for consistency.
- return ColumnInfo.UNDEFINED;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Column column = (Column) o;
- if (Build.VERSION.SDK_INT >= 20) {
- if (primaryKeyPosition != column.primaryKeyPosition) return false;
- } else {
- if (isPrimaryKey() != column.isPrimaryKey()) return false;
- }
-
- if (!name.equals(column.name)) return false;
- //noinspection SimplifiableIfStatement
- if (notNull != column.notNull) return false;
-
- // Only validate default value if it was defined in an entity, i.e. if the info
- // from the compiler itself has it. b/136019383
- if (mCreatedFrom == CREATED_FROM_ENTITY
- && column.mCreatedFrom == CREATED_FROM_DATABASE
- && (defaultValue != null && !defaultValue.equals(column.defaultValue))) {
- return false;
- } else if (mCreatedFrom == CREATED_FROM_DATABASE
- && column.mCreatedFrom == CREATED_FROM_ENTITY
- && (column.defaultValue != null && !column.defaultValue.equals(defaultValue))) {
- return false;
- } else if (mCreatedFrom != CREATED_FROM_UNKNOWN
- && mCreatedFrom == column.mCreatedFrom
- && (defaultValue != null ? !defaultValue.equals(column.defaultValue)
- : column.defaultValue != null)) {
- return false;
- }
-
- return affinity == column.affinity;
- }
-
- /**
- * Returns whether this column is part of the primary key or not.
- *
- * @return True if this column is part of the primary key, false otherwise.
- */
- public boolean isPrimaryKey() {
- return primaryKeyPosition > 0;
- }
-
- @Override
- public int hashCode() {
- int result = name.hashCode();
- result = 31 * result + affinity;
- result = 31 * result + (notNull ? 1231 : 1237);
- result = 31 * result + primaryKeyPosition;
- // Default value is not part of the hashcode since we conditionally check it for
- // equality which would break the equals + hashcode contract.
- // result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "Column{"
- + "name='" + name + '\''
- + ", type='" + type + '\''
- + ", affinity='" + affinity + '\''
- + ", notNull=" + notNull
- + ", primaryKeyPosition=" + primaryKeyPosition
- + ", defaultValue='" + defaultValue + '\''
- + '}';
- }
- }
-
- /**
- * Holds the information about an SQLite foreign key
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static class ForeignKey {
- @NonNull
- public final String referenceTable;
- @NonNull
- public final String onDelete;
- @NonNull
- public final String onUpdate;
- @NonNull
- public final List columnNames;
- @NonNull
- public final List referenceColumnNames;
-
- public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete,
- @NonNull String onUpdate,
- @NonNull List columnNames, @NonNull List referenceColumnNames) {
- this.referenceTable = referenceTable;
- this.onDelete = onDelete;
- this.onUpdate = onUpdate;
- this.columnNames = Collections.unmodifiableList(columnNames);
- this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- ForeignKey that = (ForeignKey) o;
-
- if (!referenceTable.equals(that.referenceTable)) return false;
- if (!onDelete.equals(that.onDelete)) return false;
- if (!onUpdate.equals(that.onUpdate)) return false;
- //noinspection SimplifiableIfStatement
- if (!columnNames.equals(that.columnNames)) return false;
- return referenceColumnNames.equals(that.referenceColumnNames);
- }
-
- @Override
- public int hashCode() {
- int result = referenceTable.hashCode();
- result = 31 * result + onDelete.hashCode();
- result = 31 * result + onUpdate.hashCode();
- result = 31 * result + columnNames.hashCode();
- result = 31 * result + referenceColumnNames.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- return "ForeignKey{"
- + "referenceTable='" + referenceTable + '\''
- + ", onDelete='" + onDelete + '\''
- + ", onUpdate='" + onUpdate + '\''
- + ", columnNames=" + columnNames
- + ", referenceColumnNames=" + referenceColumnNames
- + '}';
- }
- }
-
- /**
- * Temporary data holder for a foreign key row in the pragma result. We need this to ensure
- * sorting in the generated foreign key object.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- static class ForeignKeyWithSequence implements Comparable {
- final int mId;
- final int mSequence;
- final String mFrom;
- final String mTo;
-
- ForeignKeyWithSequence(int id, int sequence, String from, String to) {
- mId = id;
- mSequence = sequence;
- mFrom = from;
- mTo = to;
- }
-
- @Override
- public int compareTo(@NonNull ForeignKeyWithSequence o) {
- final int idCmp = mId - o.mId;
- if (idCmp == 0) {
- return mSequence - o.mSequence;
- } else {
- return idCmp;
- }
- }
- }
-
- /**
- * Holds the information about an SQLite index
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static class Index {
- // should match the value in Index.kt
- public static final String DEFAULT_PREFIX = "index_";
- public final String name;
- public final boolean unique;
- public final List columns;
-
- public Index(String name, boolean unique, List columns) {
- this.name = name;
- this.unique = unique;
- this.columns = columns;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Index index = (Index) o;
- if (unique != index.unique) {
- return false;
- }
- if (!columns.equals(index.columns)) {
- return false;
- }
- if (name.startsWith(Index.DEFAULT_PREFIX)) {
- return index.name.startsWith(Index.DEFAULT_PREFIX);
- } else {
- return name.equals(index.name);
- }
- }
-
- @Override
- public int hashCode() {
- int result;
- if (name.startsWith(DEFAULT_PREFIX)) {
- result = DEFAULT_PREFIX.hashCode();
- } else {
- result = name.hashCode();
- }
- result = 31 * result + (unique ? 1 : 0);
- result = 31 * result + columns.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- return "Index{"
- + "name='" + name + '\''
- + ", unique=" + unique
- + ", columns=" + columns
- + '}';
- }
- }
-}
diff --git a/app/src/main/java/androidx/room/util/ViewInfo.java b/app/src/main/java/androidx/room/util/ViewInfo.java
deleted file mode 100644
index 5e4b9b2bbe..0000000000
--- a/app/src/main/java/androidx/room/util/ViewInfo.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2018 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.util;
-
-import android.database.Cursor;
-
-import androidx.annotation.RestrictTo;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-
-/**
- * A data class that holds the information about a view.
- *
- * This derives information from sqlite_master.
- *
- * Even though SQLite column names are case insensitive, this class uses case sensitive matching.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class ViewInfo {
-
- /**
- * The view name
- */
- public final String name;
-
- /**
- * The SQL of CREATE VIEW.
- */
- public final String sql;
-
- public ViewInfo(String name, String sql) {
- this.name = name;
- this.sql = sql;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- ViewInfo viewInfo = (ViewInfo) o;
- return (name != null ? name.equals(viewInfo.name) : viewInfo.name == null)
- && (sql != null ? sql.equals(viewInfo.sql) : viewInfo.sql == null);
- }
-
- @Override
- public int hashCode() {
- int result = name != null ? name.hashCode() : 0;
- result = 31 * result + (sql != null ? sql.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "ViewInfo{"
- + "name='" + name + '\''
- + ", sql='" + sql + '\''
- + '}';
- }
-
- /**
- * Reads the view information from the given database.
- *
- * @param database The database to read the information from.
- * @param viewName The view name.
- * @return A ViewInfo containing the schema information for the provided view name.
- */
- @SuppressWarnings("SameParameterValue")
- public static ViewInfo read(SupportSQLiteDatabase database, String viewName) {
- Cursor cursor = database.query("SELECT name, sql FROM sqlite_master "
- + "WHERE type = 'view' AND name = '" + viewName + "'");
- //noinspection TryFinallyCanBeTryWithResources
- try {
- if (cursor.moveToFirst()) {
- return new ViewInfo(cursor.getString(0), cursor.getString(1));
- } else {
- return new ViewInfo(viewName, null);
- }
- } finally {
- cursor.close();
- }
- }
-}