Updated AndroidX

This commit is contained in:
M66B 2021-12-15 21:33:14 +01:00
parent b99088d1ad
commit 5a5d3ee1a0
18 changed files with 613 additions and 95 deletions

View File

@ -7,6 +7,7 @@
### Next version
* Small improvements and minor bug fixes
* Updated AndroidX
### 1.1789 - 2021-12-14

View File

@ -297,16 +297,16 @@ dependencies {
//implementation fileTree(dir: 'libs', include: ['*.jar'])
def startup_version = "1.1.0"
def annotation_version_experimental = "1.1.0" // 1.2.0-rc01
def annotation_version_experimental = "1.2.0"
def core_version = "1.6.0" // 1.7.0
def shortcuts_version = "1.0.0"
def appcompat_version = "1.3.1"
def emoji_version = "1.0.0"
def emoji_version = "1.0.1"
def activity_version = "1.4.0"
def fragment_version = "1.4.0"
def webkit_version = "1.4.0"
def recyclerview_version = "1.2.1"
def coordinatorlayout_version = "1.1.0" // 1.2.0-beta01
def coordinatorlayout_version = "1.1.0" // 1.2.0-rc01
def constraintlayout_version = "2.1.2"
def material_version = "1.4.0"
def browser_version = "1.4.0"
@ -315,8 +315,8 @@ dependencies {
def documentfile_version = "1.1.0-alpha01"
def lifecycle_version = "2.4.0"
def lifecycle_extensions_version = "2.2.0"
def room_version = "2.3.0" // 2.4.0-rc01
def sqlite_version = "2.1.0" // 2.2.0-rc01
def room_version = "2.4.0"
def sqlite_version = "2.2.0"
def requery_version = "3.36.0"
def paging_version = "2.1.2" // 3.1.0
def preference_version = "1.1.1" // 1.2.0-alpha02

View File

@ -7,6 +7,7 @@
### Next version
* Small improvements and minor bug fixes
* Updated AndroidX
### 1.1789 - 2021-12-14

View File

@ -16,7 +16,6 @@
package androidx.room;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.CharArrayBuffer;
@ -36,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.arch.core.util.Function;
import androidx.room.util.SneakyThrow;
import androidx.sqlite.db.SupportSQLiteCompat;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.SupportSQLiteQuery;
@ -440,7 +440,6 @@ final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper, Delega
});
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void setForeignKeyConstraintsEnabled(boolean enable) {
@ -464,7 +463,6 @@ final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper, Delega
+ "OpenHelper instead of on the database directly.");
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public boolean isWriteAheadLoggingEnabled() {
@ -698,27 +696,24 @@ final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper, Delega
mDelegate.setNotificationUri(cr, uri);
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.Q)
@Override
public void setNotificationUris(@NonNull ContentResolver cr,
@NonNull List<Uri> uris) {
mDelegate.setNotificationUris(cr, uris);
SupportSQLiteCompat.Api29Impl.setNotificationUris(mDelegate, cr, uris);
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public Uri getNotificationUri() {
return mDelegate.getNotificationUri();
return SupportSQLiteCompat.Api19Impl.getNotificationUri(mDelegate);
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.Q)
@Nullable
@Override
public List<Uri> getNotificationUris() {
return mDelegate.getNotificationUris();
return SupportSQLiteCompat.Api29Impl.getNotificationUris(mDelegate);
}
@Override
@ -726,11 +721,10 @@ final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper, Delega
return mDelegate.getWantsAllOnMoveCalls();
}
@SuppressLint("UnsafeNewApiCall")
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void setExtras(Bundle extras) {
mDelegate.setExtras(extras);
SupportSQLiteCompat.Api23Impl.setExtras(mDelegate, extras);
}
@Override

View File

@ -18,10 +18,12 @@ package androidx.room;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.room.migration.AutoMigrationSpec;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import java.io.File;
@ -69,6 +71,9 @@ public class DatabaseConfiguration {
@NonNull
public final List<Object> typeConverters;
@NonNull
public final List<AutoMigrationSpec> autoMigrationSpecs;
/**
* Whether Room should throw an exception for queries run on the main thread.
*/
@ -98,6 +103,15 @@ public class DatabaseConfiguration {
*/
public final boolean multiInstanceInvalidation;
/**
* Intent that should be bound to acquire the invalidation service or {@code null} if not used.
*
* @see {@link #multiInstanceInvalidation}
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public final Intent multiInstanceInvalidationServiceIntent;
/**
* If true, Room should crash if a migration is missing.
*/
@ -133,14 +147,13 @@ public class DatabaseConfiguration {
@Nullable
public final Callable<InputStream> copyFromInputStream;
/**
* 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,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
* RoomDatabase.JournalMode, Executor, Executor, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@ -170,7 +183,8 @@ public class DatabaseConfiguration {
@Nullable Set<Integer> migrationNotRequiredFrom) {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false,
requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null);
requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null,
null);
}
/**
@ -178,8 +192,8 @@ public class DatabaseConfiguration {
*
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
* RoomDatabase.JournalMode, Executor, Executor, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@ -216,7 +230,7 @@ public class DatabaseConfiguration {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, null, null, null, null, null);
migrationNotRequiredFrom, null, null, null, null, null, null);
}
/**
@ -224,8 +238,8 @@ public class DatabaseConfiguration {
*
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
* RoomDatabase.JournalMode, Executor, Executor, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@ -266,7 +280,7 @@ public class DatabaseConfiguration {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null);
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null, null);
}
/**
@ -274,8 +288,8 @@ public class DatabaseConfiguration {
*
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
* RoomDatabase.JournalMode, Executor, Executor, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@ -320,16 +334,16 @@ public class DatabaseConfiguration {
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
null, null);
null, null, null);
}
/**
* Creates a database configuration with the given values.
*
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
/**
* 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,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
* RoomDatabase.JournalMode, Executor, Executor, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@ -377,12 +391,17 @@ public class DatabaseConfiguration {
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
prepackagedDatabaseCallback, null);
prepackagedDatabaseCallback, 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, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @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.
@ -407,6 +426,7 @@ public class DatabaseConfiguration {
*
* @hide
*/
@Deprecated
@SuppressLint("LambdaLast")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
@ -426,6 +446,126 @@ public class DatabaseConfiguration {
@Nullable Callable<InputStream> copyFromInputStream,
@Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
@Nullable List<Object> typeConverters) {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
prepackagedDatabaseCallback, typeConverters, 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, Intent, boolean, boolean, Set, String, File,
* Callable, RoomDatabase.PrepackagedDatabaseCallback, List, List)}
*
* @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.
* @param copyFromInputStream The callable to get the input stream from which a
* pre-package database file will be copied from.
* @param prepackagedDatabaseCallback The pre-packaged callback.
* @param typeConverters The type converters.
* @param autoMigrationSpecs The auto migration specs.
*
* @hide
*/
@Deprecated
@SuppressLint("LambdaLast")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
@NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
@NonNull RoomDatabase.MigrationContainer migrationContainer,
@Nullable List<RoomDatabase.Callback> callbacks,
boolean allowMainThreadQueries,
@NonNull RoomDatabase.JournalMode journalMode,
@NonNull Executor queryExecutor,
@NonNull Executor transactionExecutor,
boolean multiInstanceInvalidation,
boolean requireMigration,
boolean allowDestructiveMigrationOnDowngrade,
@Nullable Set<Integer> migrationNotRequiredFrom,
@Nullable String copyFromAssetPath,
@Nullable File copyFromFile,
@Nullable Callable<InputStream> copyFromInputStream,
@Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
@Nullable List<Object> typeConverters,
@Nullable List<AutoMigrationSpec> autoMigrationSpecs) {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation ? new Intent(context,
MultiInstanceInvalidationService.class) : null,
requireMigration, allowDestructiveMigrationOnDowngrade, migrationNotRequiredFrom,
copyFromAssetPath, copyFromFile, copyFromInputStream, prepackagedDatabaseCallback,
typeConverters, autoMigrationSpecs);
}
/**
* 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 multiInstanceInvalidationServiceIntent The intent to use to bind to the
* invalidation service or {@code null} if not
* used.
* @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.
* @param copyFromInputStream The callable to get the input stream from which a
* pre-package database file will be copied from.
* @param prepackagedDatabaseCallback The pre-packaged callback.
* @param typeConverters The type converters.
* @param autoMigrationSpecs The auto migration specs.
*
* @hide
*/
@SuppressLint("LambdaLast")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
@NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
@NonNull RoomDatabase.MigrationContainer migrationContainer,
@Nullable List<RoomDatabase.Callback> callbacks,
boolean allowMainThreadQueries,
@NonNull RoomDatabase.JournalMode journalMode,
@NonNull Executor queryExecutor,
@NonNull Executor transactionExecutor,
@Nullable Intent multiInstanceInvalidationServiceIntent,
boolean requireMigration,
boolean allowDestructiveMigrationOnDowngrade,
@Nullable Set<Integer> migrationNotRequiredFrom,
@Nullable String copyFromAssetPath,
@Nullable File copyFromFile,
@Nullable Callable<InputStream> copyFromInputStream,
@Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
@Nullable List<Object> typeConverters,
@Nullable List<AutoMigrationSpec> autoMigrationSpecs) {
this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
this.context = context;
this.name = name;
@ -435,7 +575,9 @@ public class DatabaseConfiguration {
this.journalMode = journalMode;
this.queryExecutor = queryExecutor;
this.transactionExecutor = transactionExecutor;
this.multiInstanceInvalidation = multiInstanceInvalidation;
this.multiInstanceInvalidationServiceIntent =
multiInstanceInvalidationServiceIntent;
this.multiInstanceInvalidation = multiInstanceInvalidationServiceIntent != null;
this.requireMigration = requireMigration;
this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade;
this.mMigrationNotRequiredFrom = migrationNotRequiredFrom;
@ -444,6 +586,8 @@ public class DatabaseConfiguration {
this.copyFromInputStream = copyFromInputStream;
this.prepackagedDatabaseCallback = prepackagedDatabaseCallback;
this.typeConverters = typeConverters == null ? Collections.emptyList() : typeConverters;
this.autoMigrationSpecs = autoMigrationSpecs == null
? Collections.emptyList() : autoMigrationSpecs;
}
/**

View File

@ -24,6 +24,6 @@ import java.lang.annotation.Target;
/**
* APIs marked with ExperimentalRoomApi are experimental and may change.
*/
@Target({ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.METHOD})
@RequiresOptIn()
public @interface ExperimentalRoomApi {}

View File

@ -18,6 +18,7 @@ package androidx.room;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.os.Build;
@ -210,9 +211,9 @@ public class InvalidationTracker {
}
}
void startMultiInstanceInvalidation(Context context, String name) {
mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name, this,
mDatabase.getQueryExecutor());
void startMultiInstanceInvalidation(Context context, String name, Intent serviceIntent) {
mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name,
serviceIntent, this, mDatabase.getQueryExecutor());
}
void stopMultiInstanceInvalidation() {
@ -422,19 +423,15 @@ public class InvalidationTracker {
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.beginTransactionNonExclusive();
try {
invalidatedTableIds = checkUpdatedTable();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} else {
// 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.beginTransactionNonExclusive();
try {
invalidatedTableIds = checkUpdatedTable();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (IllegalStateException | SQLiteException exception) {
// may happen if db is closed. just log.

View File

@ -142,10 +142,12 @@ class MultiInstanceInvalidationClient {
* @param context The Context to be used for binding
* {@link IMultiInstanceInvalidationService}.
* @param name The name of the database file.
* @param serviceIntent The {@link Intent} used for binding
* {@link IMultiInstanceInvalidationService}.
* @param invalidationTracker The {@link InvalidationTracker}
* @param executor The background executor.
*/
MultiInstanceInvalidationClient(Context context, String name,
MultiInstanceInvalidationClient(Context context, String name, Intent serviceIntent,
InvalidationTracker invalidationTracker, Executor executor) {
mAppContext = context.getApplicationContext();
mName = name;
@ -174,8 +176,7 @@ class MultiInstanceInvalidationClient {
return true;
}
};
Intent intent = new Intent(mAppContext, MultiInstanceInvalidationService.class);
mAppContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
mAppContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
void stop() {

View File

@ -23,8 +23,8 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import java.util.HashMap;
@ -33,9 +33,12 @@ import java.util.HashMap;
* 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
* <p>The intent to launch it can be specified by
* {@link RoomDatabase.Builder#setMultiInstanceInvalidationServiceIntent}, although the service is
* defined in the manifest by default so there should be no need to override it in a normal
* situation.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@ExperimentalRoomApi
public class MultiInstanceInvalidationService extends Service {
// synthetic access
@ -128,7 +131,7 @@ public class MultiInstanceInvalidationService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
public IBinder onBind(@NonNull Intent intent) {
return mBinder;
}
}

View File

@ -19,6 +19,7 @@ package androidx.room;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
/**
* Utility class for Room.
@ -75,7 +76,9 @@ public class Room {
@SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static <T, C> T getGeneratedImplementation(@NonNull Class<C> klass,
@NonNull String suffix) {
final String fullPackage = klass.getPackage().getName();
String name = klass.getCanonicalName();
final String postPackageName = fullPackage.isEmpty()

View File

@ -19,6 +19,7 @@ package androidx.room;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Build;
import android.os.CancellationSignal;
@ -33,9 +34,11 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.WorkerThread;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.room.migration.AutoMigrationSpec;
import androidx.room.migration.Migration;
import androidx.room.util.SneakyThrow;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteCompat;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.SupportSQLiteQuery;
@ -99,6 +102,15 @@ public abstract class RoomDatabase {
@Deprecated
protected List<Callback> mCallbacks;
/**
* A map of auto migration spec classes to their provided instance.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
protected Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> mAutoMigrationSpecs;
private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
@Nullable
@ -150,7 +162,6 @@ public abstract class RoomDatabase {
// Updated later to an unmodifiable map when init is called.
private final Map<Class<?>, Object> mTypeConverters;
/**
* Gets the instance of the given Type Converter.
*
@ -174,6 +185,7 @@ public abstract class RoomDatabase {
public RoomDatabase() {
mInvalidationTracker = createInvalidationTracker();
mTypeConverters = new HashMap<>();
mAutoMigrationSpecs = new HashMap<>();
}
/**
@ -184,6 +196,47 @@ public abstract class RoomDatabase {
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
mOpenHelper = createOpenHelper(configuration);
Set<Class<? extends AutoMigrationSpec>> requiredAutoMigrationSpecs =
getRequiredAutoMigrationSpecs();
BitSet usedSpecs = new BitSet();
for (Class<? extends AutoMigrationSpec> spec : requiredAutoMigrationSpecs) {
int foundIndex = -1;
for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
providedIndex >= 0; providedIndex--
) {
Object provided = configuration.autoMigrationSpecs.get(providedIndex);
if (spec.isAssignableFrom(provided.getClass())) {
foundIndex = providedIndex;
usedSpecs.set(foundIndex);
break;
}
}
if (foundIndex < 0) {
throw new IllegalArgumentException(
"A required auto migration spec (" + spec.getCanonicalName()
+ ") is missing in the database configuration.");
}
mAutoMigrationSpecs.put(spec, configuration.autoMigrationSpecs.get(foundIndex));
}
for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
providedIndex >= 0; providedIndex--) {
if (!usedSpecs.get(providedIndex)) {
throw new IllegalArgumentException("Unexpected auto migration specs found. "
+ "Annotate AutoMigrationSpec implementation with "
+ "@ProvidedAutoMigrationSpec annotation or remove this spec from the "
+ "builder.");
}
}
List<Migration> autoMigrations = getAutoMigrations(mAutoMigrationSpecs);
for (Migration autoMigration : autoMigrations) {
boolean migrationExists = configuration.migrationContainer.getMigrations()
.containsKey(autoMigration.startVersion);
if (!migrationExists) {
configuration.migrationContainer.addMigrations(autoMigration);
}
}
// Configure SqliteCopyOpenHelper if it is available:
SQLiteCopyOpenHelper copyOpenHelper = unwrapOpenHelper(SQLiteCopyOpenHelper.class,
@ -211,9 +264,9 @@ public abstract class RoomDatabase {
mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
mWriteAheadLoggingEnabled = wal;
if (configuration.multiInstanceInvalidation) {
if (configuration.multiInstanceInvalidationServiceIntent != null) {
mInvalidationTracker.startMultiInstanceInvalidation(configuration.context,
configuration.name);
configuration.name, configuration.multiInstanceInvalidationServiceIntent);
}
Map<Class<?>, List<Class<?>>> requiredFactories = getRequiredTypeConverters();
@ -256,6 +309,22 @@ public abstract class RoomDatabase {
}
}
/**
* Returns a list of {@link Migration} of a database that have been automatically generated.
*
* @return A list of migration instances each of which is a generated autoMigration
* @param autoMigrationSpecs
*
* @hide
*/
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public List<Migration> getAutoMigrations(
@NonNull Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> autoMigrationSpecs
) {
return Collections.emptyList();
}
/**
* Unwraps (delegating) open helpers until it finds clazz, otherwise returns null.
*
@ -322,6 +391,21 @@ public abstract class RoomDatabase {
return Collections.emptyMap();
}
/**
* Returns a Set of required AutoMigrationSpec classes.
* <p>
* This is implemented by the generated code.
*
* @return Creates a set that will include all required auto migration specs for this database.
*
* @hide
*/
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public Set<Class<? extends AutoMigrationSpec>> getRequiredAutoMigrationSpecs() {
return Collections.emptySet();
}
/**
* Deletes all rows from all the tables that are registered to this database as
* {@link Database#entities()}.
@ -625,7 +709,7 @@ public abstract class RoomDatabase {
/**
* Journal modes for SQLite database.
*
* @see RoomDatabase.Builder#setJournalMode(JournalMode)
* @see Builder#setJournalMode(JournalMode)
*/
public enum JournalMode {
@ -653,7 +737,6 @@ public abstract class RoomDatabase {
* Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
* {@link #WRITE_AHEAD_LOGGING}.
*/
@SuppressLint("NewApi")
JournalMode resolve(Context context) {
if (this != AUTOMATIC) {
return this;
@ -670,7 +753,7 @@ public abstract class RoomDatabase {
private static boolean isLowRamDevice(@NonNull ActivityManager activityManager) {
if (Build.VERSION.SDK_INT >= 19) {
return activityManager.isLowRamDevice();
return SupportSQLiteCompat.Api19Impl.isLowRamDevice(activityManager);
}
return false;
}
@ -690,6 +773,7 @@ public abstract class RoomDatabase {
private QueryCallback mQueryCallback;
private Executor mQueryCallbackExecutor;
private List<Object> mTypeConverters;
private List<AutoMigrationSpec> mAutoMigrationSpecs;
/** The Executor used to run database queries. This should be background-threaded. */
private Executor mQueryExecutor;
@ -698,7 +782,7 @@ public abstract class RoomDatabase {
private SupportSQLiteOpenHelper.Factory mFactory;
private boolean mAllowMainThreadQueries;
private JournalMode mJournalMode;
private boolean mMultiInstanceInvalidation;
private Intent mMultiInstanceInvalidationIntent;
private boolean mRequireMigration;
private boolean mAllowDestructiveMigrationOnDowngrade;
@ -961,6 +1045,23 @@ public abstract class RoomDatabase {
return this;
}
/**
* Adds an auto migration spec to the builder.
*
* @param autoMigrationSpec The auto migration object that is annotated with
* {@link AutoMigrationSpec} and is declared in an {@link AutoMigration} annotation.
* @return This {@link Builder} instance.
*/
@NonNull
@SuppressWarnings("MissingGetterMatchingBuilder")
public Builder<T> addAutoMigrationSpec(@NonNull AutoMigrationSpec autoMigrationSpec) {
if (mAutoMigrationSpecs == null) {
mAutoMigrationSpecs = new ArrayList<>();
}
mAutoMigrationSpecs.add(autoMigrationSpec);
return this;
}
/**
* Disables the main thread query check for Room.
* <p>
@ -1067,7 +1168,33 @@ public abstract class RoomDatabase {
*/
@NonNull
public Builder<T> enableMultiInstanceInvalidation() {
mMultiInstanceInvalidation = mName != null;
mMultiInstanceInvalidationIntent = mName != null ? new Intent(mContext,
MultiInstanceInvalidationService.class) : null;
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 and need to point to the same
* {@link MultiInstanceInvalidationService}.
* <p>
* This is not enabled by default.
* <p>
* This does not work for in-memory databases. This does not work between database instances
* targeting different database files.
*
* @return This {@link Builder} instance.
* @param invalidationServiceIntent Intent to bind to the
* {@link MultiInstanceInvalidationService}.
*/
@SuppressWarnings("MissingGetterMatchingBuilder")
@NonNull
@ExperimentalRoomApi
public Builder<T> setMultiInstanceInvalidationServiceIntent(
@NonNull Intent invalidationServiceIntent) {
mMultiInstanceInvalidationIntent = mName != null ? invalidationServiceIntent : null;
return this;
}
@ -1346,7 +1473,7 @@ public abstract class RoomDatabase {
mJournalMode.resolve(mContext),
mQueryExecutor,
mTransactionExecutor,
mMultiInstanceInvalidation,
mMultiInstanceInvalidationIntent,
mRequireMigration,
mAllowDestructiveMigrationOnDowngrade,
mMigrationsNotRequiredFrom,
@ -1354,7 +1481,8 @@ public abstract class RoomDatabase {
mCopyFromFile,
mCopyFromInputStream,
mPrepackagedDatabaseCallback,
mTypeConverters);
mTypeConverters,
mAutoMigrationSpecs);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
@ -1380,6 +1508,18 @@ public abstract class RoomDatabase {
}
}
/**
* 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 List<Migration> migrations) {
for (Migration migration : migrations) {
addMigration(migration);
}
}
private void addMigration(Migration migration) {
final int start = migration.startVersion;
final int end = migration.endVersion;
@ -1395,6 +1535,17 @@ public abstract class RoomDatabase {
targetMap.put(end, migration);
}
/**
* Returns the map of available migrations where the key is the start version of the
* migration, and the value is a map of (end version -> Migration).
*
* @return Map of migrations keyed by the start version
*/
@NonNull
public Map<Integer, Map<Integer, Migration>> getMigrations() {
return Collections.unmodifiableMap(mMigrations);
}
/**
* Finds the list of migrations that should be run to move from {@code start} version to
* {@code end} version.

View File

@ -248,18 +248,16 @@ class SQLiteCopyOpenHelper implements SupportSQLiteOpenHelper, DelegatingOpenHel
}
private SupportSQLiteOpenHelper createFrameworkOpenHelper(File databaseFile) {
String databaseName = databaseFile.getName();
int version;
final int version;
try {
version = DBUtil.readVersion(databaseFile);
} catch (IOException e) {
throw new RuntimeException("Malformed database file, unable to read version.", e);
}
FrameworkSQLiteOpenHelperFactory factory = new FrameworkSQLiteOpenHelperFactory();
Configuration configuration = Configuration.builder(mContext)
.name(databaseName)
.callback(new Callback(version) {
.name(databaseFile.getAbsolutePath())
.callback(new Callback(Math.max(version, 1)) {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
}
@ -268,6 +266,18 @@ class SQLiteCopyOpenHelper implements SupportSQLiteOpenHelper, DelegatingOpenHel
public void onUpgrade(@NonNull SupportSQLiteDatabase db, int oldVersion,
int newVersion) {
}
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
// If pre-packaged database has a version < 1 we will open it as if it was
// version 1 because the framework open helper does not allow version < 1.
// The database will be considered as newly created and onCreate() will be
// invoked, but we do nothing and reset the version back so Room later runs
// migrations as usual.
if (version < 1) {
db.setVersion(version);
}
}
})
.build();
return factory.create(configuration);

View File

@ -16,21 +16,23 @@
package androidx.room.migration;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.room.AutoMigration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Interface for defining automatic migration strategy for Room databases.
* Interface for defining an automatic migration specification for Room databases.
* <p>
* The methods defined in this interface will be called on a background thread from the executor
* set in Room's builder. It is important to note that the methods are all in a transaction when
* it is called.
*
* @hide
* @see AutoMigration
*/
// TODO: (b/181655460) Complete code usage documentation for this class and the AutoMigration
// annotation.
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public interface AutoMigrationCallback {
public interface AutoMigrationSpec {
/**
* Handles any changes the user may want to implement after migration is completed.
* Invoked after the migration is completed.
* @param db The SQLite database.
*/
default void onPostMigrate(@NonNull SupportSQLiteDatabase db) {}
}

View File

@ -80,7 +80,7 @@
* void delete(Song song);
* }
* // File: MusicDatabase.java
* {@literal @}Database(entities = {Song.java})
* {@literal @}Database(entities = {Song.class})
* public abstract class MusicDatabase extends RoomDatabase {
* public abstract SongDao songDao();
* }

View File

@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteCompat;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteQuery;
@ -34,7 +35,9 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Database utilities for Room
@ -125,6 +128,22 @@ public class DBUtil {
}
}
/**
* Checks for foreign key violations by executing a PRAGMA foreign_key_check.
*/
public static void foreignKeyCheck(@NonNull SupportSQLiteDatabase db,
@NonNull String tableName) {
Cursor cursor = db.query("PRAGMA foreign_key_check(`" + tableName + "`)");
try {
if (cursor.getCount() > 0) {
String errorMsg = processForeignKeyCheckFailure(cursor);
throw new IllegalStateException(errorMsg);
}
} finally {
cursor.close();
}
}
/**
* Reads the user version number out of the database header from the given file.
*
@ -165,11 +184,60 @@ public class DBUtil {
@Nullable
public static CancellationSignal createCancellationSignal() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return new CancellationSignal();
return SupportSQLiteCompat.Api16Impl.createCancellationSignal();
}
return null;
}
/**
* Converts the {@link Cursor} returned in case of a foreign key violation into a detailed
* error message for debugging.
* <p>
* The foreign_key_check pragma returns one row output for each foreign key violation.
* <p>
* The cursor received has four columns for each row output. The first column is the name of
* the child table. The second column is the rowId of the row that contains the foreign key
* violation (or NULL if the child table is a WITHOUT ROWID table). The third column is the
* name of the parent table. The fourth column is the index of the specific foreign key
* constraint that failed.
*
* @param cursor Cursor containing information regarding the FK violation
* @return Error message generated containing debugging information
*/
private static String processForeignKeyCheckFailure(Cursor cursor) {
int rowCount = cursor.getCount();
String childTableName = null;
Map<String, String> fkParentTables = new HashMap<>();
while (cursor.moveToNext()) {
if (childTableName == null) {
childTableName = cursor.getString(0);
}
String constraintIndex = cursor.getString(3);
if (!fkParentTables.containsKey(constraintIndex)) {
fkParentTables.put(constraintIndex, cursor.getString(2));
}
}
StringBuilder sb = new StringBuilder();
sb.append("Foreign key violation(s) detected in '")
.append(childTableName).append("'.\n");
sb.append("Number of different violations discovered: ")
.append(fkParentTables.keySet().size()).append("\n");
sb.append("Number of rows in violation: ")
.append(rowCount).append("\n");
sb.append("Violation(s) detected in the following constraint(s):\n");
for (Map.Entry<String, String> entry : fkParentTables.entrySet()) {
sb.append("\tParent Table = ")
.append(entry.getValue());
sb.append(", Foreign Key Constraint Index = ")
.append(entry.getKey()).append("\n");
}
return sb.toString();
}
private DBUtil() {
}
}

View File

@ -237,6 +237,7 @@ public final class TableInfo {
private static Map<String, Column> readColumns(SupportSQLiteDatabase database,
String tableName) {
Cursor cursor = database
.query("PRAGMA table_info(`" + tableName + "`)");
//noinspection TryFinallyCanBeTryWithResources
@ -312,11 +313,14 @@ public final class TableInfo {
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) {
final int descColumnIndex = cursor.getColumnIndex("desc");
if (seqnoColumnIndex == -1 || cidColumnIndex == -1
|| nameColumnIndex == -1 || descColumnIndex == -1) {
// we cannot read them so better not validate any index.
return null;
}
final TreeMap<Integer, String> results = new TreeMap<>();
final TreeMap<Integer, String> columnsMap = new TreeMap<>();
final TreeMap<Integer, String> ordersMap = new TreeMap<>();
while (cursor.moveToNext()) {
int cid = cursor.getInt(cidColumnIndex);
@ -326,11 +330,16 @@ public final class TableInfo {
}
int seq = cursor.getInt(seqnoColumnIndex);
String columnName = cursor.getString(nameColumnIndex);
results.put(seq, columnName);
String order = cursor.getInt(descColumnIndex) > 0 ? "DESC" : "ASC";
columnsMap.put(seq, columnName);
ordersMap.put(seq, order);
}
final List<String> columns = new ArrayList<>(results.size());
columns.addAll(results.values());
return new Index(name, unique, columns);
final List<String> columns = new ArrayList<>(columnsMap.size());
columns.addAll(columnsMap.values());
final List<String> orders = new ArrayList<>(ordersMap.size());
orders.addAll(ordersMap.values());
return new Index(name, unique, columns, orders);
} finally {
cursor.close();
}
@ -456,15 +465,18 @@ public final class TableInfo {
// from the compiler itself has it. b/136019383
if (mCreatedFrom == CREATED_FROM_ENTITY
&& column.mCreatedFrom == CREATED_FROM_DATABASE
&& (defaultValue != null && !defaultValue.equals(column.defaultValue))) {
&& (defaultValue != null && !defaultValueEquals(defaultValue,
column.defaultValue))) {
return false;
} else if (mCreatedFrom == CREATED_FROM_DATABASE
&& column.mCreatedFrom == CREATED_FROM_ENTITY
&& (column.defaultValue != null && !column.defaultValue.equals(defaultValue))) {
&& (column.defaultValue != null && !defaultValueEquals(
column.defaultValue, defaultValue))) {
return false;
} else if (mCreatedFrom != CREATED_FROM_UNKNOWN
&& mCreatedFrom == column.mCreatedFrom
&& (defaultValue != null ? !defaultValue.equals(column.defaultValue)
&& (defaultValue != null ? !defaultValueEquals(defaultValue,
column.defaultValue)
: column.defaultValue != null)) {
return false;
}
@ -472,6 +484,56 @@ public final class TableInfo {
return affinity == column.affinity;
}
/**
* Checks if the default values provided match. Handles the special case in which the
* default value is surrounded by parenthesis (e.g. encountered in b/182284899).
*
* Surrounding parenthesis are removed by SQLite when reading from the database, hence
* this function will check if they are present in the actual value, if so, it will
* compare the two values by ignoring the surrounding parenthesis.
*
*/
public static boolean defaultValueEquals(@NonNull String actual, @Nullable String other) {
if (other == null) {
return false;
}
if (actual.equals(other)) {
return true;
} else if (containsSurroundingParenthesis(actual)) {
return actual.substring(1, actual.length() - 1).trim().equals(other);
}
return false;
}
/**
* Checks for potential surrounding parenthesis, if found, removes them and checks if
* remaining paranthesis are balanced. If so, the surrounding parenthesis are redundant,
* and returns true.
*/
private static boolean containsSurroundingParenthesis(@NonNull String actual) {
if (actual.length() == 0) {
return false;
}
int surroundingParenthesis = 0;
for (int i = 0; i < actual.length(); i++) {
char c = actual.charAt(i);
if (i == 0 && c != '(') {
return false;
}
if (c == '(') {
surroundingParenthesis++;
} else if (c == ')') {
surroundingParenthesis--;
if (surroundingParenthesis == 0 && i != actual.length() - 1) {
return false;
}
}
}
return surroundingParenthesis == 0;
}
/**
* Returns whether this column is part of the primary key or not.
*
@ -614,11 +676,22 @@ public final class TableInfo {
public final String name;
public final boolean unique;
public final List<String> columns;
public final List<String> orders;
/**
* @deprecated Use {@link #Index(String, boolean, List, List)}
*/
public Index(String name, boolean unique, List<String> columns) {
this(name, unique, columns, null);
}
public Index(String name, boolean unique, List<String> columns, List<String> orders) {
this.name = name;
this.unique = unique;
this.columns = columns;
this.orders = orders == null || orders.size() == 0
? Collections.nCopies(columns.size(), androidx.room.Index.Order.ASC.name())
: orders;
}
@Override
@ -633,6 +706,9 @@ public final class TableInfo {
if (!columns.equals(index.columns)) {
return false;
}
if (!orders.equals(index.orders)) {
return false;
}
if (name.startsWith(Index.DEFAULT_PREFIX)) {
return index.name.startsWith(Index.DEFAULT_PREFIX);
} else {
@ -650,6 +726,7 @@ public final class TableInfo {
}
result = 31 * result + (unique ? 1 : 0);
result = 31 * result + columns.hashCode();
result = 31 * result + orders.hashCode();
return result;
}
@ -659,6 +736,7 @@ public final class TableInfo {
+ "name='" + name + '\''
+ ", unique=" + unique
+ ", columns=" + columns
+ ", orders=" + orders
+ '}';
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2021 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.nio.ByteBuffer;
import java.util.UUID;
/**
* UUID / byte[] two-way conversion utility for Room
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public final class UUIDUtil {
// private constructor to prevent instantiation
private UUIDUtil() {}
/**
* Converts a 16-bytes array BLOB into a UUID pojo
*
* @param bytes byte array stored in database as BLOB
* @return a UUID object created based on the provided byte array
*/
@NonNull
public static UUID convertByteToUUID(@NonNull byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
long firstLong = buffer.getLong();
long secondLong = buffer.getLong();
return new UUID(firstLong, secondLong);
}
/**
* Converts a UUID pojo into a 16-bytes array to store into database as BLOB
*
* @param uuid the UUID pojo
* @return a byte array to store into database
*/
@NonNull
public static byte[] convertUUIDToByte(@NonNull UUID uuid) {
byte[] bytes = new byte[16];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
return buffer.array();
}
}

View File

@ -7,6 +7,7 @@
### Next version
* Small improvements and minor bug fixes
* Updated AndroidX
### 1.1789 - 2021-12-14