509 lines
20 KiB
Java
509 lines
20 KiB
Java
package androidx.room;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.ActivityManager;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.os.CancellationSignal;
|
|
import android.os.Looper;
|
|
import android.util.Log;
|
|
import androidx.annotation.CallSuper;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
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.Iterator;
|
|
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;
|
|
public abstract class RoomDatabase {
|
|
private static final String DB_IMPL_SUFFIX = "_Impl";
|
|
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX})
|
|
public static final int MAX_BIND_PARAMETER_CNT = 999;
|
|
private boolean mAllowMainThreadQueries;
|
|
private final Map<String, Object> mBackingFieldMap = new ConcurrentHashMap();
|
|
@Nullable
|
|
@Deprecated
|
|
public List<Callback> mCallbacks;
|
|
private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
|
|
@Deprecated
|
|
public volatile SupportSQLiteDatabase mDatabase;
|
|
private final InvalidationTracker mInvalidationTracker = createInvalidationTracker();
|
|
private SupportSQLiteOpenHelper mOpenHelper;
|
|
private Executor mQueryExecutor;
|
|
private final ThreadLocal<Integer> mSuspendingTransactionId = new ThreadLocal<>();
|
|
private Executor mTransactionExecutor;
|
|
public boolean mWriteAheadLoggingEnabled;
|
|
|
|
public static class Builder<T extends RoomDatabase> {
|
|
private boolean mAllowDestructiveMigrationOnDowngrade;
|
|
private boolean mAllowMainThreadQueries;
|
|
private ArrayList<Callback> mCallbacks;
|
|
private final Context mContext;
|
|
private String mCopyFromAssetPath;
|
|
private File mCopyFromFile;
|
|
private final Class<T> mDatabaseClass;
|
|
private SupportSQLiteOpenHelper.Factory mFactory;
|
|
private JournalMode mJournalMode = JournalMode.AUTOMATIC;
|
|
private final MigrationContainer mMigrationContainer = new MigrationContainer();
|
|
private Set<Integer> mMigrationStartAndEndVersions;
|
|
private Set<Integer> mMigrationsNotRequiredFrom;
|
|
private boolean mMultiInstanceInvalidation;
|
|
private final String mName;
|
|
private Executor mQueryExecutor;
|
|
private boolean mRequireMigration = true;
|
|
private Executor mTransactionExecutor;
|
|
|
|
public Builder(@NonNull Context context, @NonNull Class<T> cls, @Nullable String str) {
|
|
this.mContext = context;
|
|
this.mDatabaseClass = cls;
|
|
this.mName = str;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> addCallback(@NonNull Callback callback) {
|
|
if (this.mCallbacks == null) {
|
|
this.mCallbacks = new ArrayList<>();
|
|
}
|
|
this.mCallbacks.add(callback);
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> addMigrations(@NonNull Migration... migrationArr) {
|
|
if (this.mMigrationStartAndEndVersions == null) {
|
|
this.mMigrationStartAndEndVersions = new HashSet();
|
|
}
|
|
for (Migration migration : migrationArr) {
|
|
this.mMigrationStartAndEndVersions.add(Integer.valueOf(migration.startVersion));
|
|
this.mMigrationStartAndEndVersions.add(Integer.valueOf(migration.endVersion));
|
|
}
|
|
this.mMigrationContainer.addMigrations(migrationArr);
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> allowMainThreadQueries() {
|
|
this.mAllowMainThreadQueries = true;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
@SuppressLint({"RestrictedApi"})
|
|
public T build() {
|
|
Executor executor;
|
|
if (this.mContext == null) {
|
|
throw new IllegalArgumentException("Cannot provide null context for the database.");
|
|
} else if (this.mDatabaseClass != null) {
|
|
Executor executor2 = this.mQueryExecutor;
|
|
if (executor2 == null && this.mTransactionExecutor == null) {
|
|
Executor iOThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
|
|
this.mTransactionExecutor = iOThreadExecutor;
|
|
this.mQueryExecutor = iOThreadExecutor;
|
|
} else if (executor2 != null && this.mTransactionExecutor == null) {
|
|
this.mTransactionExecutor = executor2;
|
|
} else if (executor2 == null && (executor = this.mTransactionExecutor) != null) {
|
|
this.mQueryExecutor = executor;
|
|
}
|
|
Set<Integer> set = this.mMigrationStartAndEndVersions;
|
|
if (!(set == null || this.mMigrationsNotRequiredFrom == null)) {
|
|
for (Integer num : set) {
|
|
if (this.mMigrationsNotRequiredFrom.contains(num)) {
|
|
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: " + num);
|
|
}
|
|
}
|
|
}
|
|
if (this.mFactory == null) {
|
|
this.mFactory = new FrameworkSQLiteOpenHelperFactory();
|
|
}
|
|
String str = this.mCopyFromAssetPath;
|
|
if (!(str == null && this.mCopyFromFile == null)) {
|
|
if (this.mName == null) {
|
|
throw new IllegalArgumentException("Cannot create from asset or file for an in-memory database.");
|
|
} else if (str == null || this.mCopyFromFile == null) {
|
|
this.mFactory = new SQLiteCopyOpenHelperFactory(str, this.mCopyFromFile, this.mFactory);
|
|
} else {
|
|
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.");
|
|
}
|
|
}
|
|
Context context = this.mContext;
|
|
DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(context, this.mName, this.mFactory, this.mMigrationContainer, this.mCallbacks, this.mAllowMainThreadQueries, this.mJournalMode.resolve(context), this.mQueryExecutor, this.mTransactionExecutor, this.mMultiInstanceInvalidation, this.mRequireMigration, this.mAllowDestructiveMigrationOnDowngrade, this.mMigrationsNotRequiredFrom, this.mCopyFromAssetPath, this.mCopyFromFile);
|
|
T t = (T) ((RoomDatabase) Room.getGeneratedImplementation(this.mDatabaseClass, "_Impl"));
|
|
t.init(databaseConfiguration);
|
|
return t;
|
|
} else {
|
|
throw new IllegalArgumentException("Must provide an abstract class that extends RoomDatabase");
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> createFromAsset(@NonNull String str) {
|
|
this.mCopyFromAssetPath = str;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> createFromFile(@NonNull File file) {
|
|
this.mCopyFromFile = file;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> enableMultiInstanceInvalidation() {
|
|
this.mMultiInstanceInvalidation = this.mName != null;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> fallbackToDestructiveMigration() {
|
|
this.mRequireMigration = false;
|
|
this.mAllowDestructiveMigrationOnDowngrade = true;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> fallbackToDestructiveMigrationFrom(int... iArr) {
|
|
if (this.mMigrationsNotRequiredFrom == null) {
|
|
this.mMigrationsNotRequiredFrom = new HashSet(iArr.length);
|
|
}
|
|
for (int i : iArr) {
|
|
this.mMigrationsNotRequiredFrom.add(Integer.valueOf(i));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> fallbackToDestructiveMigrationOnDowngrade() {
|
|
this.mRequireMigration = true;
|
|
this.mAllowDestructiveMigrationOnDowngrade = true;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
|
|
this.mFactory = factory;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> setJournalMode(@NonNull JournalMode journalMode) {
|
|
this.mJournalMode = journalMode;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> setQueryExecutor(@NonNull Executor executor) {
|
|
this.mQueryExecutor = executor;
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder<T> setTransactionExecutor(@NonNull Executor executor) {
|
|
this.mTransactionExecutor = executor;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public static abstract class Callback {
|
|
public void onCreate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
|
|
}
|
|
|
|
public void onDestructiveMigration(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
|
|
}
|
|
|
|
public void onOpen(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
|
|
}
|
|
}
|
|
|
|
public enum JournalMode {
|
|
AUTOMATIC,
|
|
TRUNCATE,
|
|
WRITE_AHEAD_LOGGING;
|
|
|
|
private static boolean isLowRamDevice(@NonNull ActivityManager activityManager) {
|
|
return activityManager.isLowRamDevice();
|
|
}
|
|
|
|
@SuppressLint({"NewApi"})
|
|
public JournalMode resolve(Context context) {
|
|
if (this != AUTOMATIC) {
|
|
return this;
|
|
}
|
|
ActivityManager activityManager = (ActivityManager) context.getSystemService("activity");
|
|
return (activityManager == null || isLowRamDevice(activityManager)) ? TRUNCATE : WRITE_AHEAD_LOGGING;
|
|
}
|
|
}
|
|
|
|
public static class MigrationContainer {
|
|
private HashMap<Integer, TreeMap<Integer, Migration>> mMigrations = new HashMap<>();
|
|
|
|
private void addMigration(Migration migration) {
|
|
int i = migration.startVersion;
|
|
int i2 = migration.endVersion;
|
|
TreeMap<Integer, Migration> treeMap = this.mMigrations.get(Integer.valueOf(i));
|
|
if (treeMap == null) {
|
|
treeMap = new TreeMap<>();
|
|
this.mMigrations.put(Integer.valueOf(i), treeMap);
|
|
}
|
|
Migration migration2 = treeMap.get(Integer.valueOf(i2));
|
|
if (migration2 != null) {
|
|
Log.w("ROOM", "Overriding migration " + migration2 + " with " + migration);
|
|
}
|
|
treeMap.put(Integer.valueOf(i2), migration);
|
|
}
|
|
|
|
private List<Migration> findUpMigrationPath(List<Migration> list, boolean z2, int i, int i2) {
|
|
boolean z3;
|
|
do {
|
|
if (z2) {
|
|
if (i >= i2) {
|
|
return list;
|
|
}
|
|
} else if (i <= i2) {
|
|
return list;
|
|
}
|
|
TreeMap<Integer, Migration> treeMap = this.mMigrations.get(Integer.valueOf(i));
|
|
if (treeMap != null) {
|
|
Iterator<Integer> it = (z2 ? treeMap.descendingKeySet() : treeMap.keySet()).iterator();
|
|
while (true) {
|
|
z3 = true;
|
|
boolean z4 = false;
|
|
if (!it.hasNext()) {
|
|
z3 = false;
|
|
continue;
|
|
break;
|
|
}
|
|
int intValue = it.next().intValue();
|
|
if (!z2 ? !(intValue < i2 || intValue >= i) : !(intValue > i2 || intValue <= i)) {
|
|
z4 = true;
|
|
continue;
|
|
}
|
|
if (z4) {
|
|
list.add(treeMap.get(Integer.valueOf(intValue)));
|
|
i = intValue;
|
|
continue;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
} while (z3);
|
|
return null;
|
|
}
|
|
|
|
public void addMigrations(@NonNull Migration... migrationArr) {
|
|
for (Migration migration : migrationArr) {
|
|
addMigration(migration);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public List<Migration> findMigrationPath(int i, int i2) {
|
|
if (i == i2) {
|
|
return Collections.emptyList();
|
|
}
|
|
return findUpMigrationPath(new ArrayList(), i2 > i, i, i2);
|
|
}
|
|
}
|
|
|
|
private static boolean isMainThread() {
|
|
return Looper.getMainLooper().getThread() == Thread.currentThread();
|
|
}
|
|
|
|
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX})
|
|
public void assertNotMainThread() {
|
|
if (!this.mAllowMainThreadQueries && isMainThread()) {
|
|
throw new IllegalStateException("Cannot access database on the main thread since it may potentially lock the UI for a long period of time.");
|
|
}
|
|
}
|
|
|
|
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
|
|
public void assertNotSuspendingTransaction() {
|
|
if (!inTransaction() && this.mSuspendingTransactionId.get() != null) {
|
|
throw new IllegalStateException("Cannot access database on a different coroutine context inherited from a suspending transaction.");
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public void beginTransaction() {
|
|
assertNotMainThread();
|
|
SupportSQLiteDatabase writableDatabase = this.mOpenHelper.getWritableDatabase();
|
|
this.mInvalidationTracker.syncTriggers(writableDatabase);
|
|
writableDatabase.beginTransaction();
|
|
}
|
|
|
|
@WorkerThread
|
|
public abstract void clearAllTables();
|
|
|
|
public void close() {
|
|
if (isOpen()) {
|
|
ReentrantReadWriteLock.WriteLock writeLock = this.mCloseLock.writeLock();
|
|
try {
|
|
writeLock.lock();
|
|
this.mInvalidationTracker.stopMultiInstanceInvalidation();
|
|
this.mOpenHelper.close();
|
|
} finally {
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
public SupportSQLiteStatement compileStatement(@NonNull String str) {
|
|
assertNotMainThread();
|
|
assertNotSuspendingTransaction();
|
|
return this.mOpenHelper.getWritableDatabase().compileStatement(str);
|
|
}
|
|
|
|
@NonNull
|
|
public abstract InvalidationTracker createInvalidationTracker();
|
|
|
|
@NonNull
|
|
public abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration databaseConfiguration);
|
|
|
|
@Deprecated
|
|
public void endTransaction() {
|
|
this.mOpenHelper.getWritableDatabase().endTransaction();
|
|
if (!inTransaction()) {
|
|
this.mInvalidationTracker.refreshVersionsAsync();
|
|
}
|
|
}
|
|
|
|
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
|
|
public Map<String, Object> getBackingFieldMap() {
|
|
return this.mBackingFieldMap;
|
|
}
|
|
|
|
public Lock getCloseLock() {
|
|
return this.mCloseLock.readLock();
|
|
}
|
|
|
|
@NonNull
|
|
public InvalidationTracker getInvalidationTracker() {
|
|
return this.mInvalidationTracker;
|
|
}
|
|
|
|
@NonNull
|
|
public SupportSQLiteOpenHelper getOpenHelper() {
|
|
return this.mOpenHelper;
|
|
}
|
|
|
|
@NonNull
|
|
public Executor getQueryExecutor() {
|
|
return this.mQueryExecutor;
|
|
}
|
|
|
|
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
|
|
public ThreadLocal<Integer> getSuspendingTransactionId() {
|
|
return this.mSuspendingTransactionId;
|
|
}
|
|
|
|
@NonNull
|
|
public Executor getTransactionExecutor() {
|
|
return this.mTransactionExecutor;
|
|
}
|
|
|
|
public boolean inTransaction() {
|
|
return this.mOpenHelper.getWritableDatabase().inTransaction();
|
|
}
|
|
|
|
@CallSuper
|
|
public void init(@NonNull DatabaseConfiguration databaseConfiguration) {
|
|
SupportSQLiteOpenHelper createOpenHelper = createOpenHelper(databaseConfiguration);
|
|
this.mOpenHelper = createOpenHelper;
|
|
if (createOpenHelper instanceof SQLiteCopyOpenHelper) {
|
|
((SQLiteCopyOpenHelper) createOpenHelper).setDatabaseConfiguration(databaseConfiguration);
|
|
}
|
|
boolean z2 = databaseConfiguration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
|
|
this.mOpenHelper.setWriteAheadLoggingEnabled(z2);
|
|
this.mCallbacks = databaseConfiguration.callbacks;
|
|
this.mQueryExecutor = databaseConfiguration.queryExecutor;
|
|
this.mTransactionExecutor = new TransactionExecutor(databaseConfiguration.transactionExecutor);
|
|
this.mAllowMainThreadQueries = databaseConfiguration.allowMainThreadQueries;
|
|
this.mWriteAheadLoggingEnabled = z2;
|
|
if (databaseConfiguration.multiInstanceInvalidation) {
|
|
this.mInvalidationTracker.startMultiInstanceInvalidation(databaseConfiguration.context, databaseConfiguration.name);
|
|
}
|
|
}
|
|
|
|
public void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
|
|
this.mInvalidationTracker.internalInit(supportSQLiteDatabase);
|
|
}
|
|
|
|
public boolean isOpen() {
|
|
SupportSQLiteDatabase supportSQLiteDatabase = this.mDatabase;
|
|
return supportSQLiteDatabase != null && supportSQLiteDatabase.isOpen();
|
|
}
|
|
|
|
@NonNull
|
|
public Cursor query(@NonNull SupportSQLiteQuery supportSQLiteQuery) {
|
|
return query(supportSQLiteQuery, (CancellationSignal) null);
|
|
}
|
|
|
|
@NonNull
|
|
public Cursor query(@NonNull SupportSQLiteQuery supportSQLiteQuery, @Nullable CancellationSignal cancellationSignal) {
|
|
assertNotMainThread();
|
|
assertNotSuspendingTransaction();
|
|
return cancellationSignal != null ? this.mOpenHelper.getWritableDatabase().query(supportSQLiteQuery, cancellationSignal) : this.mOpenHelper.getWritableDatabase().query(supportSQLiteQuery);
|
|
}
|
|
|
|
@NonNull
|
|
public Cursor query(@NonNull String str, @Nullable Object[] objArr) {
|
|
return this.mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(str, objArr));
|
|
}
|
|
|
|
public <V> V runInTransaction(@NonNull Callable<V> callable) {
|
|
beginTransaction();
|
|
try {
|
|
V call = callable.call();
|
|
setTransactionSuccessful();
|
|
endTransaction();
|
|
return call;
|
|
} catch (RuntimeException e) {
|
|
throw e;
|
|
} catch (Exception e2) {
|
|
SneakyThrow.reThrow(e2);
|
|
endTransaction();
|
|
return null;
|
|
} catch (Throwable th) {
|
|
endTransaction();
|
|
throw th;
|
|
}
|
|
}
|
|
|
|
public void runInTransaction(@NonNull Runnable runnable) {
|
|
beginTransaction();
|
|
try {
|
|
runnable.run();
|
|
setTransactionSuccessful();
|
|
} finally {
|
|
endTransaction();
|
|
}
|
|
}
|
|
|
|
@Deprecated
|
|
public void setTransactionSuccessful() {
|
|
this.mOpenHelper.getWritableDatabase().setTransactionSuccessful();
|
|
}
|
|
}
|