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 mBackingFieldMap = new ConcurrentHashMap(); @Nullable @Deprecated public List 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 mSuspendingTransactionId = new ThreadLocal<>(); private Executor mTransactionExecutor; public boolean mWriteAheadLoggingEnabled; public static class Builder { private boolean mAllowDestructiveMigrationOnDowngrade; private boolean mAllowMainThreadQueries; private ArrayList mCallbacks; private final Context mContext; private String mCopyFromAssetPath; private File mCopyFromFile; private final Class mDatabaseClass; private SupportSQLiteOpenHelper.Factory mFactory; private JournalMode mJournalMode = JournalMode.AUTOMATIC; private final MigrationContainer mMigrationContainer = new MigrationContainer(); private Set mMigrationStartAndEndVersions; private Set 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 cls, @Nullable String str) { this.mContext = context; this.mDatabaseClass = cls; this.mName = str; } @NonNull public Builder addCallback(@NonNull Callback callback) { if (this.mCallbacks == null) { this.mCallbacks = new ArrayList<>(); } this.mCallbacks.add(callback); return this; } @NonNull public Builder 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 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 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 createFromAsset(@NonNull String str) { this.mCopyFromAssetPath = str; return this; } @NonNull public Builder createFromFile(@NonNull File file) { this.mCopyFromFile = file; return this; } @NonNull public Builder enableMultiInstanceInvalidation() { this.mMultiInstanceInvalidation = this.mName != null; return this; } @NonNull public Builder fallbackToDestructiveMigration() { this.mRequireMigration = false; this.mAllowDestructiveMigrationOnDowngrade = true; return this; } @NonNull public Builder 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 fallbackToDestructiveMigrationOnDowngrade() { this.mRequireMigration = true; this.mAllowDestructiveMigrationOnDowngrade = true; return this; } @NonNull public Builder openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) { this.mFactory = factory; return this; } @NonNull public Builder setJournalMode(@NonNull JournalMode journalMode) { this.mJournalMode = journalMode; return this; } @NonNull public Builder setQueryExecutor(@NonNull Executor executor) { this.mQueryExecutor = executor; return this; } @NonNull public Builder 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> mMigrations = new HashMap<>(); private void addMigration(Migration migration) { int i = migration.startVersion; int i2 = migration.endVersion; TreeMap 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 findUpMigrationPath(List list, boolean z2, int i, int i2) { boolean z3; do { if (z2) { if (i >= i2) { return list; } } else if (i <= i2) { return list; } TreeMap treeMap = this.mMigrations.get(Integer.valueOf(i)); if (treeMap != null) { Iterator 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 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 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 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 runInTransaction(@NonNull Callable 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(); } }