1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room;
18 
19 import android.annotation.SuppressLint;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.os.Build;
24 import android.util.Log;
25 
26 import androidx.annotation.CallSuper;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.RequiresApi;
30 import androidx.annotation.RestrictTo;
31 import androidx.annotation.WorkerThread;
32 import androidx.collection.SparseArrayCompat;
33 import androidx.core.app.ActivityManagerCompat;
34 import androidx.arch.core.executor.ArchTaskExecutor;
35 import androidx.room.migration.Migration;
36 import androidx.sqlite.db.SimpleSQLiteQuery;
37 import androidx.sqlite.db.SupportSQLiteDatabase;
38 import androidx.sqlite.db.SupportSQLiteOpenHelper;
39 import androidx.sqlite.db.SupportSQLiteQuery;
40 import androidx.sqlite.db.SupportSQLiteStatement;
41 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.concurrent.Callable;
49 import java.util.concurrent.locks.Lock;
50 import java.util.concurrent.locks.ReentrantLock;
51 
52 /**
53  * Base class for all Room databases. All classes that are annotated with {@link Database} must
54  * extend this class.
55  * <p>
56  * RoomDatabase provides direct access to the underlying database implementation but you should
57  * prefer using {@link Dao} classes.
58  *
59  * @see Database
60  */
61 //@SuppressWarnings({"unused", "WeakerAccess"})
62 public abstract class RoomDatabase {
63     private static final String DB_IMPL_SUFFIX = "_Impl";
64     /**
65      * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
66      *
67      * @hide
68      */
69     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
70     public static final int MAX_BIND_PARAMETER_CNT = 999;
71     // set by the generated open helper.
72     protected volatile SupportSQLiteDatabase mDatabase;
73     private SupportSQLiteOpenHelper mOpenHelper;
74     private final InvalidationTracker mInvalidationTracker;
75     private boolean mAllowMainThreadQueries;
76     boolean mWriteAheadLoggingEnabled;
77 
78     @Nullable
79     protected List<Callback> mCallbacks;
80 
81     private final ReentrantLock mCloseLock = new ReentrantLock();
82 
83     /**
84      * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
85      * querying database updates.
86      *
87      * @return The lock for {@link #close()}.
88      */
getCloseLock()89     Lock getCloseLock() {
90         return mCloseLock;
91     }
92 
93     /**
94      * Creates a RoomDatabase.
95      * <p>
96      * You cannot create an instance of a database, instead, you should acquire it via
97      * {@link Room#databaseBuilder(Context, Class, String)} or
98      * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
99      */
RoomDatabase()100     public RoomDatabase() {
101         mInvalidationTracker = createInvalidationTracker();
102     }
103 
104     /**
105      * Called by {@link Room} when it is initialized.
106      *
107      * @param configuration The database configuration.
108      */
109     @CallSuper
init(@onNull DatabaseConfiguration configuration)110     public void init(@NonNull DatabaseConfiguration configuration) {
111         mOpenHelper = createOpenHelper(configuration);
112         boolean wal = false;
113         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
114             wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
115             mOpenHelper.setWriteAheadLoggingEnabled(wal);
116         }
117         mCallbacks = configuration.callbacks;
118         mAllowMainThreadQueries = configuration.allowMainThreadQueries;
119         mWriteAheadLoggingEnabled = wal;
120     }
121 
122     /**
123      * Returns the SQLite open helper used by this database.
124      *
125      * @return The SQLite open helper used by this database.
126      */
127     @NonNull
getOpenHelper()128     public SupportSQLiteOpenHelper getOpenHelper() {
129         return mOpenHelper;
130     }
131 
132     /**
133      * Creates the open helper to access the database. Generated class already implements this
134      * method.
135      * Note that this method is called when the RoomDatabase is initialized.
136      *
137      * @param config The configuration of the Room database.
138      * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
139      */
140     @NonNull
createOpenHelper(DatabaseConfiguration config)141     protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
142 
143     /**
144      * Called when the RoomDatabase is created.
145      * <p>
146      * This is already implemented by the generated code.
147      *
148      * @return Creates a new InvalidationTracker.
149      */
150     @NonNull
createInvalidationTracker()151     protected abstract InvalidationTracker createInvalidationTracker();
152 
153     /**
154      * Deletes all rows from all the tables that are registered to this database as
155      * {@link Database#entities()}.
156      * <p>
157      * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}.
158      * <p>
159      * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the
160      * data is completely erased. The space will be reclaimed by the system if the amount surpasses
161      * the threshold of database file size.
162      *
163      * @see <a href="https://www.sqlite.org/fileformat.html">Database File Format</a>
164      */
165     @WorkerThread
clearAllTables()166     public abstract void clearAllTables();
167 
168     /**
169      * Returns true if database connection is open and initialized.
170      *
171      * @return true if the database connection is open, false otherwise.
172      */
isOpen()173     public boolean isOpen() {
174         final SupportSQLiteDatabase db = mDatabase;
175         return db != null && db.isOpen();
176     }
177 
178     /**
179      * Closes the database if it is already open.
180      */
close()181     public void close() {
182         if (isOpen()) {
183             try {
184                 mCloseLock.lock();
185                 mOpenHelper.close();
186             } finally {
187                 mCloseLock.unlock();
188             }
189         }
190     }
191 
192     /**
193      * Asserts that we are not on the main thread.
194      *
195      * @hide
196      */
197     @SuppressWarnings("WeakerAccess")
198     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
199     // used in generated code
assertNotMainThread()200     public void assertNotMainThread() {
201         if (mAllowMainThreadQueries) {
202             return;
203         }
204         if (ArchTaskExecutor.getInstance().isMainThread()) {
205             throw new IllegalStateException("Cannot access database on the main thread since"
206                     + " it may potentially lock the UI for a long period of time.");
207         }
208     }
209 
210     // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
211     // methods we are using and also helps unit tests to mock this class without mocking
212     // all SQLite database methods.
213 
214     /**
215      * Convenience method to query the database with arguments.
216      *
217      * @param query The sql query
218      * @param args The bind arguments for the placeholders in the query
219      *
220      * @return A Cursor obtained by running the given query in the Room database.
221      */
query(String query, @Nullable Object[] args)222     public Cursor query(String query, @Nullable Object[] args) {
223         return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
224     }
225 
226     /**
227      * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
228      *
229      * @param query The Query which includes the SQL and a bind callback for bind arguments.
230      * @return Result of the query.
231      */
query(SupportSQLiteQuery query)232     public Cursor query(SupportSQLiteQuery query) {
233         assertNotMainThread();
234         return mOpenHelper.getWritableDatabase().query(query);
235     }
236 
237     /**
238      * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
239      *
240      * @param sql The query to compile.
241      * @return The compiled query.
242      */
compileStatement(@onNull String sql)243     public SupportSQLiteStatement compileStatement(@NonNull String sql) {
244         assertNotMainThread();
245         return mOpenHelper.getWritableDatabase().compileStatement(sql);
246     }
247 
248     /**
249      * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
250      */
beginTransaction()251     public void beginTransaction() {
252         assertNotMainThread();
253         SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
254         mInvalidationTracker.syncTriggers(database);
255         database.beginTransaction();
256     }
257 
258     /**
259      * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
260      */
endTransaction()261     public void endTransaction() {
262         mOpenHelper.getWritableDatabase().endTransaction();
263         if (!inTransaction()) {
264             // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
265             // endTransaction call to do it.
266             mInvalidationTracker.refreshVersionsAsync();
267         }
268     }
269 
270     /**
271      * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
272      */
setTransactionSuccessful()273     public void setTransactionSuccessful() {
274         mOpenHelper.getWritableDatabase().setTransactionSuccessful();
275     }
276 
277     /**
278      * Executes the specified {@link Runnable} in a database transaction. The transaction will be
279      * marked as successful unless an exception is thrown in the {@link Runnable}.
280      *
281      * @param body The piece of code to execute.
282      */
runInTransaction(@onNull Runnable body)283     public void runInTransaction(@NonNull Runnable body) {
284         beginTransaction();
285         try {
286             body.run();
287             setTransactionSuccessful();
288         } finally {
289             endTransaction();
290         }
291     }
292 
293     /**
294      * Executes the specified {@link Callable} in a database transaction. The transaction will be
295      * marked as successful unless an exception is thrown in the {@link Callable}.
296      *
297      * @param body The piece of code to execute.
298      * @param <V>  The type of the return value.
299      * @return The value returned from the {@link Callable}.
300      */
runInTransaction(@onNull Callable<V> body)301     public <V> V runInTransaction(@NonNull Callable<V> body) {
302         beginTransaction();
303         try {
304             V result = body.call();
305             setTransactionSuccessful();
306             return result;
307         } catch (RuntimeException e) {
308             throw e;
309         } catch (Exception e) {
310             throw new RuntimeException("Exception in transaction", e);
311         } finally {
312             endTransaction();
313         }
314     }
315 
316     /**
317      * Called by the generated code when database is open.
318      * <p>
319      * You should never call this method manually.
320      *
321      * @param db The database instance.
322      */
internalInitInvalidationTracker(@onNull SupportSQLiteDatabase db)323     protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
324         mInvalidationTracker.internalInit(db);
325     }
326 
327     /**
328      * Returns the invalidation tracker for this database.
329      * <p>
330      * You can use the invalidation tracker to get notified when certain tables in the database
331      * are modified.
332      *
333      * @return The invalidation tracker for the database.
334      */
335     @NonNull
getInvalidationTracker()336     public InvalidationTracker getInvalidationTracker() {
337         return mInvalidationTracker;
338     }
339 
340     /**
341      * Returns true if current thread is in a transaction.
342      *
343      * @return True if there is an active transaction in current thread, false otherwise.
344      * @see SupportSQLiteDatabase#inTransaction()
345      */
346     @SuppressWarnings("WeakerAccess")
inTransaction()347     public boolean inTransaction() {
348         return mOpenHelper.getWritableDatabase().inTransaction();
349     }
350 
351     /**
352      * Journal modes for SQLite database.
353      *
354      * @see RoomDatabase.Builder#setJournalMode(JournalMode)
355      */
356     public enum JournalMode {
357 
358         /**
359          * Let Room choose the journal mode. This is the default value when no explicit value is
360          * specified.
361          * <p>
362          * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16
363          * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used.
364          */
365         AUTOMATIC,
366 
367         /**
368          * Truncate journal mode.
369          */
370         TRUNCATE,
371 
372         /**
373          * Write-Ahead Logging mode.
374          */
375         @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
376         WRITE_AHEAD_LOGGING;
377 
378         /**
379          * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
380          * {@link #WRITE_AHEAD_LOGGING}.
381          */
382         @SuppressLint("NewApi")
resolve(Context context)383         JournalMode resolve(Context context) {
384             if (this != AUTOMATIC) {
385                 return this;
386             }
387             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
388                 ActivityManager manager = (ActivityManager)
389                         context.getSystemService(Context.ACTIVITY_SERVICE);
390                 if (manager != null && !ActivityManagerCompat.isLowRamDevice(manager)) {
391                     return WRITE_AHEAD_LOGGING;
392                 }
393             }
394             return TRUNCATE;
395         }
396     }
397 
398     /**
399      * Builder for RoomDatabase.
400      *
401      * @param <T> The type of the abstract database class.
402      */
403     public static class Builder<T extends RoomDatabase> {
404         private final Class<T> mDatabaseClass;
405         private final String mName;
406         private final Context mContext;
407         private ArrayList<Callback> mCallbacks;
408 
409         private SupportSQLiteOpenHelper.Factory mFactory;
410         private boolean mAllowMainThreadQueries;
411         private JournalMode mJournalMode;
412         private boolean mRequireMigration;
413         /**
414          * Migrations, mapped by from-to pairs.
415          */
416         private final MigrationContainer mMigrationContainer;
417         private Set<Integer> mMigrationsNotRequiredFrom;
418         /**
419          * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in
420          * {@link #addMigrations(Migration...)} for later validation that makes those versions don't
421          * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(int...)}.
422          */
423         private Set<Integer> mMigrationStartAndEndVersions;
424 
Builder(@onNull Context context, @NonNull Class<T> klass, @Nullable String name)425         Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
426             mContext = context;
427             mDatabaseClass = klass;
428             mName = name;
429             mJournalMode = JournalMode.AUTOMATIC;
430             mRequireMigration = true;
431             mMigrationContainer = new MigrationContainer();
432         }
433 
434         /**
435          * Sets the database factory. If not set, it defaults to
436          * {@link FrameworkSQLiteOpenHelperFactory}.
437          *
438          * @param factory The factory to use to access the database.
439          * @return this
440          */
441         @NonNull
openHelperFactory(@ullable SupportSQLiteOpenHelper.Factory factory)442         public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
443             mFactory = factory;
444             return this;
445         }
446 
447         /**
448          * Adds a migration to the builder.
449          * <p>
450          * Each Migration has a start and end versions and Room runs these migrations to bring the
451          * database to the latest version.
452          * <p>
453          * If a migration item is missing between current version and the latest version, Room
454          * will clear the database and recreate so even if you have no changes between 2 versions,
455          * you should still provide a Migration object to the builder.
456          * <p>
457          * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
458          * going version 3 to 5 without going to version 4). If Room opens a database at version
459          * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
460          * 3 to 5 instead of 3 to 4 and 4 to 5.
461          *
462          * @param migrations The migration object that can modify the database and to the necessary
463          *                   changes.
464          * @return this
465          */
466         @NonNull
addMigrations(@onNull Migration... migrations)467         public Builder<T> addMigrations(@NonNull  Migration... migrations) {
468             if (mMigrationStartAndEndVersions == null) {
469                 mMigrationStartAndEndVersions = new HashSet<>();
470             }
471             for (Migration migration: migrations) {
472                 mMigrationStartAndEndVersions.add(migration.startVersion);
473                 mMigrationStartAndEndVersions.add(migration.endVersion);
474             }
475 
476             mMigrationContainer.addMigrations(migrations);
477             return this;
478         }
479 
480         /**
481          * Disables the main thread query check for Room.
482          * <p>
483          * Room ensures that Database is never accessed on the main thread because it may lock the
484          * main thread and trigger an ANR. If you need to access the database from the main thread,
485          * you should always use async alternatives or manually move the call to a background
486          * thread.
487          * <p>
488          * You may want to turn this check off for testing.
489          *
490          * @return this
491          */
492         @NonNull
allowMainThreadQueries()493         public Builder<T> allowMainThreadQueries() {
494             mAllowMainThreadQueries = true;
495             return this;
496         }
497 
498         /**
499          * Sets the journal mode for this database.
500          *
501          * <p>
502          * This value is ignored if the builder is initialized with
503          * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
504          * <p>
505          * The journal mode should be consistent across multiple instances of
506          * {@link RoomDatabase} for a single SQLite database file.
507          * <p>
508          * The default value is {@link JournalMode#AUTOMATIC}.
509          *
510          * @param journalMode The journal mode.
511          * @return this
512          */
513         @NonNull
setJournalMode(@onNull JournalMode journalMode)514         public Builder<T> setJournalMode(@NonNull JournalMode journalMode) {
515             mJournalMode = journalMode;
516             return this;
517         }
518 
519         /**
520          * Allows Room to destructively recreate database tables if {@link Migration}s that would
521          * migrate old database schemas to the latest schema version are not found.
522          * <p>
523          * When the database version on the device does not match the latest schema version, Room
524          * runs necessary {@link Migration}s on the database.
525          * <p>
526          * If it cannot find the set of {@link Migration}s that will bring the database to the
527          * current version, it will throw an {@link IllegalStateException}.
528          * <p>
529          * You can call this method to change this behavior to re-create the database instead of
530          * crashing.
531          * <p>
532          * Note that this will delete all of the data in the database tables managed by Room.
533          *
534          * @return this
535          */
536         @NonNull
fallbackToDestructiveMigration()537         public Builder<T> fallbackToDestructiveMigration() {
538             mRequireMigration = false;
539             return this;
540         }
541 
542         /**
543          * Informs Room that it is allowed to destructively recreate database tables from specific
544          * starting schema versions.
545          * <p>
546          * This functionality is the same as that provided by
547          * {@link #fallbackToDestructiveMigration()}, except that this method allows the
548          * specification of a set of schema versions for which destructive recreation is allowed.
549          * <p>
550          * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want
551          * to allow destructive migrations from some schema versions while still taking advantage
552          * of exceptions being thrown due to unintentionally missing migrations.
553          * <p>
554          * Note: No versions passed to this method may also exist as either starting or ending
555          * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a
556          * version passed to this method is found as a starting or ending version in a Migration, an
557          * exception will be thrown.
558          *
559          * @param startVersions The set of schema versions from which Room should use a destructive
560          *                      migration.
561          * @return this
562          */
563         @NonNull
fallbackToDestructiveMigrationFrom(int... startVersions)564         public Builder<T> fallbackToDestructiveMigrationFrom(int... startVersions) {
565             if (mMigrationsNotRequiredFrom == null) {
566                 mMigrationsNotRequiredFrom = new HashSet<>(startVersions.length);
567             }
568             for (int startVersion : startVersions) {
569                 mMigrationsNotRequiredFrom.add(startVersion);
570             }
571             return this;
572         }
573 
574         /**
575          * Adds a {@link Callback} to this database.
576          *
577          * @param callback The callback.
578          * @return this
579          */
580         @NonNull
addCallback(@onNull Callback callback)581         public Builder<T> addCallback(@NonNull Callback callback) {
582             if (mCallbacks == null) {
583                 mCallbacks = new ArrayList<>();
584             }
585             mCallbacks.add(callback);
586             return this;
587         }
588 
589         /**
590          * Creates the databases and initializes it.
591          * <p>
592          * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
593          * triggers.
594          *
595          * @return A new database instance.
596          */
597         @NonNull
build()598         public T build() {
599             //noinspection ConstantConditions
600             if (mContext == null) {
601                 throw new IllegalArgumentException("Cannot provide null context for the database.");
602             }
603             //noinspection ConstantConditions
604             if (mDatabaseClass == null) {
605                 throw new IllegalArgumentException("Must provide an abstract class that"
606                         + " extends RoomDatabase");
607             }
608 
609             if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
610                 for (Integer version : mMigrationStartAndEndVersions) {
611                     if (mMigrationsNotRequiredFrom.contains(version)) {
612                         throw new IllegalArgumentException(
613                                 "Inconsistency detected. A Migration was supplied to "
614                                         + "addMigration(Migration... migrations) that has a start "
615                                         + "or end version equal to a start version supplied to "
616                                         + "fallbackToDestructiveMigrationFrom(int... "
617                                         + "startVersions). Start version: "
618                                         + version);
619                     }
620                 }
621             }
622 
623             if (mFactory == null) {
624                 mFactory = new FrameworkSQLiteOpenHelperFactory();
625             }
626             DatabaseConfiguration configuration =
627                     new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
628                             mCallbacks, mAllowMainThreadQueries,
629                             mJournalMode.resolve(mContext),
630                             mRequireMigration, mMigrationsNotRequiredFrom);
631             T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
632             db.init(configuration);
633             return db;
634         }
635     }
636 
637     /**
638      * A container to hold migrations. It also allows querying its contents to find migrations
639      * between two versions.
640      */
641     public static class MigrationContainer {
642         private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
643                 new SparseArrayCompat<>();
644 
645         /**
646          * Adds the given migrations to the list of available migrations. If 2 migrations have the
647          * same start-end versions, the latter migration overrides the previous one.
648          *
649          * @param migrations List of available migrations.
650          */
addMigrations(@onNull Migration... migrations)651         public void addMigrations(@NonNull Migration... migrations) {
652             for (Migration migration : migrations) {
653                 addMigration(migration);
654             }
655         }
656 
addMigration(Migration migration)657         private void addMigration(Migration migration) {
658             final int start = migration.startVersion;
659             final int end = migration.endVersion;
660             SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
661             if (targetMap == null) {
662                 targetMap = new SparseArrayCompat<>();
663                 mMigrations.put(start, targetMap);
664             }
665             Migration existing = targetMap.get(end);
666             if (existing != null) {
667                 Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
668             }
669             targetMap.append(end, migration);
670         }
671 
672         /**
673          * Finds the list of migrations that should be run to move from {@code start} version to
674          * {@code end} version.
675          *
676          * @param start The current database version
677          * @param end   The target database version
678          * @return An ordered list of {@link Migration} objects that should be run to migrate
679          * between the given versions. If a migration path cannot be found, returns {@code null}.
680          */
681         @SuppressWarnings("WeakerAccess")
682         @Nullable
findMigrationPath(int start, int end)683         public List<Migration> findMigrationPath(int start, int end) {
684             if (start == end) {
685                 return Collections.emptyList();
686             }
687             boolean migrateUp = end > start;
688             List<Migration> result = new ArrayList<>();
689             return findUpMigrationPath(result, migrateUp, start, end);
690         }
691 
findUpMigrationPath(List<Migration> result, boolean upgrade, int start, int end)692         private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
693                 int start, int end) {
694             final int searchDirection = upgrade ? -1 : 1;
695             while (upgrade ? start < end : start > end) {
696                 SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
697                 if (targetNodes == null) {
698                     return null;
699                 }
700                 // keys are ordered so we can start searching from one end of them.
701                 final int size = targetNodes.size();
702                 final int firstIndex;
703                 final int lastIndex;
704 
705                 if (upgrade) {
706                     firstIndex = size - 1;
707                     lastIndex = -1;
708                 } else {
709                     firstIndex = 0;
710                     lastIndex = size;
711                 }
712                 boolean found = false;
713                 for (int i = firstIndex; i != lastIndex; i += searchDirection) {
714                     final int targetVersion = targetNodes.keyAt(i);
715                     final boolean shouldAddToPath;
716                     if (upgrade) {
717                         shouldAddToPath = targetVersion <= end && targetVersion > start;
718                     } else {
719                         shouldAddToPath = targetVersion >= end && targetVersion < start;
720                     }
721                     if (shouldAddToPath) {
722                         result.add(targetNodes.valueAt(i));
723                         start = targetVersion;
724                         found = true;
725                         break;
726                     }
727                 }
728                 if (!found) {
729                     return null;
730                 }
731             }
732             return result;
733         }
734     }
735 
736     /**
737      * Callback for {@link RoomDatabase}.
738      */
739     public abstract static class Callback {
740 
741         /**
742          * Called when the database is created for the first time. This is called after all the
743          * tables are created.
744          *
745          * @param db The database.
746          */
747         public void onCreate(@NonNull SupportSQLiteDatabase db) {
748         }
749 
750         /**
751          * Called when the database has been opened.
752          *
753          * @param db The database.
754          */
755         public void onOpen(@NonNull SupportSQLiteDatabase db) {
756         }
757     }
758 }
759