Loading packages/SettingsLib/DataStore/Android.bp +6 −1 Original line number Original line Diff line number Diff line Loading @@ -2,12 +2,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], default_applicable_licenses: ["frameworks_base_license"], } } filegroup { name: "SettingsLibDataStore-srcs", srcs: ["src/**/*"], } android_library { android_library { name: "SettingsLibDataStore", name: "SettingsLibDataStore", defaults: [ defaults: [ "SettingsLintDefaults", "SettingsLintDefaults", ], ], srcs: ["src/**/*"], srcs: [":SettingsLibDataStore-srcs"], static_libs: [ static_libs: [ "androidx.annotation_annotation", "androidx.annotation_annotation", "androidx.collection_collection-ktx", "androidx.collection_collection-ktx", Loading packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt +6 −6 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.settingslib.datastore import android.app.backup.BackupDataInputStream import android.app.backup.BackupDataInputStream import android.content.Context import android.content.Context import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import java.io.File import java.io.File import java.io.InputStream import java.io.InputStream import java.io.OutputStream import java.io.OutputStream Loading @@ -33,11 +34,9 @@ import java.util.zip.CheckedInputStream */ */ internal class BackupRestoreFileArchiver( internal class BackupRestoreFileArchiver( private val context: Context, private val context: Context, private val fileStorages: List<BackupRestoreFileStorage>, @get:VisibleForTesting internal val fileStorages: List<BackupRestoreFileStorage>, override val name: String, ) : BackupRestoreStorage() { ) : BackupRestoreStorage() { override val name: String get() = "file_archiver" override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = fileStorages.map { it.toBackupRestoreEntity() } fileStorages.map { it.toBackupRestoreEntity() } Loading Loading @@ -88,7 +87,8 @@ internal class BackupRestoreFileArchiver( } } } } private fun BackupRestoreFileStorage.toBackupRestoreEntity() = @VisibleForTesting internal fun BackupRestoreFileStorage.toBackupRestoreEntity() = object : BackupRestoreEntity { object : BackupRestoreEntity { override val key: String override val key: String get() = storageFilePath get() = storageFilePath Loading @@ -107,7 +107,7 @@ private fun BackupRestoreFileStorage.toBackupRestoreEntity() = Log.i(LOG_TAG, "[$name] $key not exist") Log.i(LOG_TAG, "[$name] $key not exist") return EntityBackupResult.DELETE return EntityBackupResult.DELETE } } val codec = codec() ?: defaultCodec() val codec = defaultCodec() // MUST close to flush the data // MUST close to flush the data wrapBackupOutputStream(codec, outputStream).use { stream -> wrapBackupOutputStream(codec, outputStream).use { stream -> val bytesCopied = file.inputStream().use { it.copyTo(stream) } val bytesCopied = file.inputStream().use { it.copyTo(stream) } Loading packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt +17 −7 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupHelper import android.app.backup.BackupHelper import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import androidx.collection.MutableScatterMap import androidx.collection.MutableScatterMap import com.google.common.io.ByteStreams import com.google.common.io.ByteStreams import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream Loading Loading @@ -60,10 +61,11 @@ abstract class BackupRestoreStorage : BackupHelper { * * * Map key is the entity key, map value is the checksum of backup data. * Map key is the entity key, map value is the checksum of backup data. */ */ protected val entityStates = MutableScatterMap<String, Long>() @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) val entityStates = MutableScatterMap<String, Long>() /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */ /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */ private var entities: List<BackupRestoreEntity>? = null @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null /** Entities to back up and restore. */ /** Entities to back up and restore. */ abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> Loading @@ -76,7 +78,7 @@ abstract class BackupRestoreStorage : BackupHelper { data: BackupDataOutput, data: BackupDataOutput, newState: ParcelFileDescriptor, newState: ParcelFileDescriptor, ) { ) { oldState.readEntityStates(entityStates) readEntityStates(oldState, entityStates) val backupContext = BackupContext(data) val backupContext = BackupContext(data) if (!enableBackup(backupContext)) { if (!enableBackup(backupContext)) { Log.i(LOG_TAG, "[$name] Backup disabled") Log.i(LOG_TAG, "[$name] Backup disabled") Loading @@ -94,7 +96,10 @@ abstract class BackupRestoreStorage : BackupHelper { val codec = entity.codec() ?: defaultCodec() val codec = entity.codec() ?: defaultCodec() val result = val result = try { try { entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream)) // MUST close to flush all data wrapBackupOutputStream(codec, checkedOutputStream).use { entity.backup(backupContext, it) } } catch (exception: Exception) { } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) continue continue Loading Loading @@ -191,9 +196,13 @@ abstract class BackupRestoreStorage : BackupHelper { /** Callbacks when restore finished. */ /** Callbacks when restore finished. */ open fun onRestoreFinished() {} open fun onRestoreFinished() {} private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) { @VisibleForTesting internal fun readEntityStates( parcelFileDescriptor: ParcelFileDescriptor?, state: MutableScatterMap<String, Long>, ) { state.clear() state.clear() if (this == null) return val fileDescriptor = parcelFileDescriptor?.fileDescriptor ?: return // do not close the streams // do not close the streams val fileInputStream = FileInputStream(fileDescriptor) val fileInputStream = FileInputStream(fileDescriptor) val dataInputStream = DataInputStream(fileInputStream) val dataInputStream = DataInputStream(fileInputStream) Loading Loading @@ -233,6 +242,7 @@ abstract class BackupRestoreStorage : BackupHelper { dataOutputStream.writeUTF(key) dataOutputStream.writeUTF(key) dataOutputStream.writeLong(value) dataOutputStream.writeLong(value) } } dataOutputStream.flush() } catch (exception: Exception) { } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to write state file", exception) Log.e(LOG_TAG, "[$name] Fail to write state file", exception) } } Loading @@ -241,7 +251,7 @@ abstract class BackupRestoreStorage : BackupHelper { } } companion object { companion object { private const val STATE_VERSION: Byte = 0 internal const val STATE_VERSION: Byte = 0 /** Checksum for entity backup data. */ /** Checksum for entity backup data. */ fun createChecksum(): Checksum = CRC32() fun createChecksum(): Checksum = CRC32() Loading packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +16 −6 Original line number Original line Diff line number Diff line Loading @@ -21,23 +21,32 @@ import android.app.backup.BackupAgentHelper import android.app.backup.BackupManager import android.app.backup.BackupManager import android.content.Context import android.content.Context import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap /** Manager of [BackupRestoreStorage]. */ /** Manager of [BackupRestoreStorage]. */ class BackupRestoreStorageManager private constructor(private val application: Application) { class BackupRestoreStorageManager private constructor(private val application: Application) { private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() @VisibleForTesting internal val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() private val executor = MoreExecutors.directExecutor() private val executor = MoreExecutors.directExecutor() /** /** * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * * * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver]. * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver], * specify [fileArchiverName] to avoid key prefix conflict if needed. * * * @param backupAgentHelper backup agent helper to add helpers * @param fileArchiverName key prefix of the [BackupRestoreFileArchiver], the value must not be * changed in future * @see BackupAgentHelper.addHelper * @see BackupAgentHelper.addHelper */ */ fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { @JvmOverloads fun addBackupAgentHelpers( backupAgentHelper: BackupAgentHelper, fileArchiverName: String = "file_archiver", ) { val fileStorages = mutableListOf<BackupRestoreFileStorage>() val fileStorages = mutableListOf<BackupRestoreFileStorage>() for ((keyPrefix, storageWrapper) in storageWrappers) { for ((keyPrefix, storageWrapper) in storageWrappers) { val storage = storageWrapper.storage val storage = storageWrapper.storage Loading @@ -48,7 +57,7 @@ class BackupRestoreStorageManager private constructor(private val application: A } } } } // Always add file archiver even fileStorages is empty to handle forward compatibility // Always add file archiver even fileStorages is empty to handle forward compatibility val fileArchiver = BackupRestoreFileArchiver(application, fileStorages) val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, fileArchiverName) backupAgentHelper.addHelper(fileArchiver.name, fileArchiver) backupAgentHelper.addHelper(fileArchiver.name, fileArchiver) } } Loading Loading @@ -106,7 +115,8 @@ class BackupRestoreStorageManager private constructor(private val application: A /** Returns storage with given name, exception is raised if not found. */ /** Returns storage with given name, exception is raised if not found. */ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage private inner class StorageWrapper(val storage: BackupRestoreStorage) : @VisibleForTesting internal inner class StorageWrapper(val storage: BackupRestoreStorage) : Observer, KeyedObserver<Any?> { Observer, KeyedObserver<Any?> { init { init { when (storage) { when (storage) { Loading Loading @@ -139,7 +149,7 @@ class BackupRestoreStorageManager private constructor(private val application: A LOG_TAG, LOG_TAG, "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" ) ) BackupManager.dataChanged(application.packageName) BackupManager(application).dataChanged() } } fun removeObserver() { fun removeObserver() { Loading packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +32 −14 Original line number Original line Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences import android.os.Build import android.os.Build import android.util.Log import android.util.Log import androidx.core.content.ContextCompat import androidx.annotation.VisibleForTesting import java.io.File import java.io.File private fun defaultVerbose() = Build.TYPE == "eng" /** /** * [SharedPreferences] based storage. * [SharedPreferences] based storage. * * Loading @@ -43,24 +45,35 @@ import java.io.File * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param filter Filter of key/value pairs for backup and restore. * @param filter Filter of key/value pairs for backup and restore. */ */ class SharedPreferencesStorage open class SharedPreferencesStorage @JvmOverloads @JvmOverloads constructor( constructor( context: Context, context: Context, override val name: String, override val name: String, mode: Int, @get:VisibleForTesting internal val sharedPreferences: SharedPreferences, private val verbose: Boolean = (Build.TYPE == "eng"), private val codec: BackupCodec? = null, private val verbose: Boolean = defaultVerbose(), private val filter: (String, Any?) -> Boolean = { _, _ -> true }, private val filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : ) : BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), KeyedObservable<String> by KeyedDataObservable() { KeyedObservable<String> by KeyedDataObservable() { private val sharedPreferences = context.getSharedPreferences(name, mode) @JvmOverloads constructor( context: Context, name: String, mode: Int, codec: BackupCodec? = null, verbose: Boolean = defaultVerbose(), filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter) /** Name of the intermediate SharedPreferences. */ /** Name of the intermediate SharedPreferences. */ private val intermediateName: String @VisibleForTesting internal val intermediateName: String get() = "_br_$name" get() = "_br_$name" @Suppress("DEPRECATION") private val intermediateSharedPreferences: SharedPreferences private val intermediateSharedPreferences: SharedPreferences get() { get() { // use MODE_MULTI_PROCESS to ensure a reload // use MODE_MULTI_PROCESS to ensure a reload Loading @@ -82,12 +95,15 @@ constructor( sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) } } override fun defaultCodec() = codec ?: super.defaultCodec() override val backupFile: File override val backupFile: File // use a different file to avoid multi-thread file write // use a different file to avoid multi-thread file write get() = context.getSharedPreferencesFile(intermediateName) get() = context.getSharedPreferencesFile(intermediateName) override fun prepareBackup(file: File) { override fun prepareBackup(file: File) { val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup") val editor = mergeSharedPreferences(intermediateSharedPreferences, sharedPreferences.all, "Backup") // commit to ensure data is write to disk synchronously // commit to ensure data is write to disk synchronously if (!editor.commit()) { if (!editor.commit()) { Log.w(LOG_TAG, "[$name] fail to commit") Log.w(LOG_TAG, "[$name] fail to commit") Loading @@ -104,8 +120,8 @@ constructor( // observers consistently once restore finished. // observers consistently once restore finished. sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) val restored = intermediateSharedPreferences val restored = intermediateSharedPreferences val editor = sharedPreferences.merge(restored.all, "Restore") val editor = mergeSharedPreferences(sharedPreferences, restored.all, "Restore") editor.apply() // apply to avoid blocking editor.commit() // commit to avoid race condition sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) // clear the intermediate SharedPreferences // clear the intermediate SharedPreferences restored.delete(intermediateName) restored.delete(intermediateName) Loading @@ -115,7 +131,7 @@ constructor( if (deleteSharedPreferences(name)) { if (deleteSharedPreferences(name)) { Log.i(LOG_TAG, "SharedPreferences $name deleted") Log.i(LOG_TAG, "SharedPreferences $name deleted") } else { } else { edit().clear().apply() edit().clear().commit() // commit to avoid potential race condition } } } } Loading @@ -126,11 +142,13 @@ constructor( false false } } private fun SharedPreferences.merge( @VisibleForTesting internal open fun mergeSharedPreferences( sharedPreferences: SharedPreferences, entries: Map<String, Any?>, entries: Map<String, Any?>, operation: String operation: String, ): SharedPreferences.Editor { ): SharedPreferences.Editor { val editor = edit() val editor = sharedPreferences.edit() for ((key, value) in entries) { for ((key, value) in entries) { if (!filter.invoke(key, value)) { if (!filter.invoke(key, value)) { if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value") if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value") Loading Loading @@ -184,7 +202,7 @@ constructor( companion object { companion object { private fun Context.getSharedPreferencesFilePath(name: String): String { private fun Context.getSharedPreferencesFilePath(name: String): String { val file = getSharedPreferencesFile(name) val file = getSharedPreferencesFile(name) return file.relativeTo(ContextCompat.getDataDir(this)!!).toString() return file.relativeTo(dataDirCompat).toString() } } /** Returns the absolute path of shared preferences file. */ /** Returns the absolute path of shared preferences file. */ Loading Loading
packages/SettingsLib/DataStore/Android.bp +6 −1 Original line number Original line Diff line number Diff line Loading @@ -2,12 +2,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], default_applicable_licenses: ["frameworks_base_license"], } } filegroup { name: "SettingsLibDataStore-srcs", srcs: ["src/**/*"], } android_library { android_library { name: "SettingsLibDataStore", name: "SettingsLibDataStore", defaults: [ defaults: [ "SettingsLintDefaults", "SettingsLintDefaults", ], ], srcs: ["src/**/*"], srcs: [":SettingsLibDataStore-srcs"], static_libs: [ static_libs: [ "androidx.annotation_annotation", "androidx.annotation_annotation", "androidx.collection_collection-ktx", "androidx.collection_collection-ktx", Loading
packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt +6 −6 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.settingslib.datastore import android.app.backup.BackupDataInputStream import android.app.backup.BackupDataInputStream import android.content.Context import android.content.Context import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import java.io.File import java.io.File import java.io.InputStream import java.io.InputStream import java.io.OutputStream import java.io.OutputStream Loading @@ -33,11 +34,9 @@ import java.util.zip.CheckedInputStream */ */ internal class BackupRestoreFileArchiver( internal class BackupRestoreFileArchiver( private val context: Context, private val context: Context, private val fileStorages: List<BackupRestoreFileStorage>, @get:VisibleForTesting internal val fileStorages: List<BackupRestoreFileStorage>, override val name: String, ) : BackupRestoreStorage() { ) : BackupRestoreStorage() { override val name: String get() = "file_archiver" override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = fileStorages.map { it.toBackupRestoreEntity() } fileStorages.map { it.toBackupRestoreEntity() } Loading Loading @@ -88,7 +87,8 @@ internal class BackupRestoreFileArchiver( } } } } private fun BackupRestoreFileStorage.toBackupRestoreEntity() = @VisibleForTesting internal fun BackupRestoreFileStorage.toBackupRestoreEntity() = object : BackupRestoreEntity { object : BackupRestoreEntity { override val key: String override val key: String get() = storageFilePath get() = storageFilePath Loading @@ -107,7 +107,7 @@ private fun BackupRestoreFileStorage.toBackupRestoreEntity() = Log.i(LOG_TAG, "[$name] $key not exist") Log.i(LOG_TAG, "[$name] $key not exist") return EntityBackupResult.DELETE return EntityBackupResult.DELETE } } val codec = codec() ?: defaultCodec() val codec = defaultCodec() // MUST close to flush the data // MUST close to flush the data wrapBackupOutputStream(codec, outputStream).use { stream -> wrapBackupOutputStream(codec, outputStream).use { stream -> val bytesCopied = file.inputStream().use { it.copyTo(stream) } val bytesCopied = file.inputStream().use { it.copyTo(stream) } Loading
packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt +17 −7 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupHelper import android.app.backup.BackupHelper import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import androidx.collection.MutableScatterMap import androidx.collection.MutableScatterMap import com.google.common.io.ByteStreams import com.google.common.io.ByteStreams import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream Loading Loading @@ -60,10 +61,11 @@ abstract class BackupRestoreStorage : BackupHelper { * * * Map key is the entity key, map value is the checksum of backup data. * Map key is the entity key, map value is the checksum of backup data. */ */ protected val entityStates = MutableScatterMap<String, Long>() @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) val entityStates = MutableScatterMap<String, Long>() /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */ /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */ private var entities: List<BackupRestoreEntity>? = null @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null /** Entities to back up and restore. */ /** Entities to back up and restore. */ abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> Loading @@ -76,7 +78,7 @@ abstract class BackupRestoreStorage : BackupHelper { data: BackupDataOutput, data: BackupDataOutput, newState: ParcelFileDescriptor, newState: ParcelFileDescriptor, ) { ) { oldState.readEntityStates(entityStates) readEntityStates(oldState, entityStates) val backupContext = BackupContext(data) val backupContext = BackupContext(data) if (!enableBackup(backupContext)) { if (!enableBackup(backupContext)) { Log.i(LOG_TAG, "[$name] Backup disabled") Log.i(LOG_TAG, "[$name] Backup disabled") Loading @@ -94,7 +96,10 @@ abstract class BackupRestoreStorage : BackupHelper { val codec = entity.codec() ?: defaultCodec() val codec = entity.codec() ?: defaultCodec() val result = val result = try { try { entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream)) // MUST close to flush all data wrapBackupOutputStream(codec, checkedOutputStream).use { entity.backup(backupContext, it) } } catch (exception: Exception) { } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception) continue continue Loading Loading @@ -191,9 +196,13 @@ abstract class BackupRestoreStorage : BackupHelper { /** Callbacks when restore finished. */ /** Callbacks when restore finished. */ open fun onRestoreFinished() {} open fun onRestoreFinished() {} private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) { @VisibleForTesting internal fun readEntityStates( parcelFileDescriptor: ParcelFileDescriptor?, state: MutableScatterMap<String, Long>, ) { state.clear() state.clear() if (this == null) return val fileDescriptor = parcelFileDescriptor?.fileDescriptor ?: return // do not close the streams // do not close the streams val fileInputStream = FileInputStream(fileDescriptor) val fileInputStream = FileInputStream(fileDescriptor) val dataInputStream = DataInputStream(fileInputStream) val dataInputStream = DataInputStream(fileInputStream) Loading Loading @@ -233,6 +242,7 @@ abstract class BackupRestoreStorage : BackupHelper { dataOutputStream.writeUTF(key) dataOutputStream.writeUTF(key) dataOutputStream.writeLong(value) dataOutputStream.writeLong(value) } } dataOutputStream.flush() } catch (exception: Exception) { } catch (exception: Exception) { Log.e(LOG_TAG, "[$name] Fail to write state file", exception) Log.e(LOG_TAG, "[$name] Fail to write state file", exception) } } Loading @@ -241,7 +251,7 @@ abstract class BackupRestoreStorage : BackupHelper { } } companion object { companion object { private const val STATE_VERSION: Byte = 0 internal const val STATE_VERSION: Byte = 0 /** Checksum for entity backup data. */ /** Checksum for entity backup data. */ fun createChecksum(): Checksum = CRC32() fun createChecksum(): Checksum = CRC32() Loading
packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +16 −6 Original line number Original line Diff line number Diff line Loading @@ -21,23 +21,32 @@ import android.app.backup.BackupAgentHelper import android.app.backup.BackupManager import android.app.backup.BackupManager import android.content.Context import android.content.Context import android.util.Log import android.util.Log import androidx.annotation.VisibleForTesting import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap /** Manager of [BackupRestoreStorage]. */ /** Manager of [BackupRestoreStorage]. */ class BackupRestoreStorageManager private constructor(private val application: Application) { class BackupRestoreStorageManager private constructor(private val application: Application) { private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() @VisibleForTesting internal val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() private val executor = MoreExecutors.directExecutor() private val executor = MoreExecutors.directExecutor() /** /** * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * * * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver]. * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver], * specify [fileArchiverName] to avoid key prefix conflict if needed. * * * @param backupAgentHelper backup agent helper to add helpers * @param fileArchiverName key prefix of the [BackupRestoreFileArchiver], the value must not be * changed in future * @see BackupAgentHelper.addHelper * @see BackupAgentHelper.addHelper */ */ fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { @JvmOverloads fun addBackupAgentHelpers( backupAgentHelper: BackupAgentHelper, fileArchiverName: String = "file_archiver", ) { val fileStorages = mutableListOf<BackupRestoreFileStorage>() val fileStorages = mutableListOf<BackupRestoreFileStorage>() for ((keyPrefix, storageWrapper) in storageWrappers) { for ((keyPrefix, storageWrapper) in storageWrappers) { val storage = storageWrapper.storage val storage = storageWrapper.storage Loading @@ -48,7 +57,7 @@ class BackupRestoreStorageManager private constructor(private val application: A } } } } // Always add file archiver even fileStorages is empty to handle forward compatibility // Always add file archiver even fileStorages is empty to handle forward compatibility val fileArchiver = BackupRestoreFileArchiver(application, fileStorages) val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, fileArchiverName) backupAgentHelper.addHelper(fileArchiver.name, fileArchiver) backupAgentHelper.addHelper(fileArchiver.name, fileArchiver) } } Loading Loading @@ -106,7 +115,8 @@ class BackupRestoreStorageManager private constructor(private val application: A /** Returns storage with given name, exception is raised if not found. */ /** Returns storage with given name, exception is raised if not found. */ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage private inner class StorageWrapper(val storage: BackupRestoreStorage) : @VisibleForTesting internal inner class StorageWrapper(val storage: BackupRestoreStorage) : Observer, KeyedObserver<Any?> { Observer, KeyedObserver<Any?> { init { init { when (storage) { when (storage) { Loading Loading @@ -139,7 +149,7 @@ class BackupRestoreStorageManager private constructor(private val application: A LOG_TAG, LOG_TAG, "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" ) ) BackupManager.dataChanged(application.packageName) BackupManager(application).dataChanged() } } fun removeObserver() { fun removeObserver() { Loading
packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +32 −14 Original line number Original line Diff line number Diff line Loading @@ -20,9 +20,11 @@ import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences import android.os.Build import android.os.Build import android.util.Log import android.util.Log import androidx.core.content.ContextCompat import androidx.annotation.VisibleForTesting import java.io.File import java.io.File private fun defaultVerbose() = Build.TYPE == "eng" /** /** * [SharedPreferences] based storage. * [SharedPreferences] based storage. * * Loading @@ -43,24 +45,35 @@ import java.io.File * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param filter Filter of key/value pairs for backup and restore. * @param filter Filter of key/value pairs for backup and restore. */ */ class SharedPreferencesStorage open class SharedPreferencesStorage @JvmOverloads @JvmOverloads constructor( constructor( context: Context, context: Context, override val name: String, override val name: String, mode: Int, @get:VisibleForTesting internal val sharedPreferences: SharedPreferences, private val verbose: Boolean = (Build.TYPE == "eng"), private val codec: BackupCodec? = null, private val verbose: Boolean = defaultVerbose(), private val filter: (String, Any?) -> Boolean = { _, _ -> true }, private val filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : ) : BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), KeyedObservable<String> by KeyedDataObservable() { KeyedObservable<String> by KeyedDataObservable() { private val sharedPreferences = context.getSharedPreferences(name, mode) @JvmOverloads constructor( context: Context, name: String, mode: Int, codec: BackupCodec? = null, verbose: Boolean = defaultVerbose(), filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter) /** Name of the intermediate SharedPreferences. */ /** Name of the intermediate SharedPreferences. */ private val intermediateName: String @VisibleForTesting internal val intermediateName: String get() = "_br_$name" get() = "_br_$name" @Suppress("DEPRECATION") private val intermediateSharedPreferences: SharedPreferences private val intermediateSharedPreferences: SharedPreferences get() { get() { // use MODE_MULTI_PROCESS to ensure a reload // use MODE_MULTI_PROCESS to ensure a reload Loading @@ -82,12 +95,15 @@ constructor( sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) } } override fun defaultCodec() = codec ?: super.defaultCodec() override val backupFile: File override val backupFile: File // use a different file to avoid multi-thread file write // use a different file to avoid multi-thread file write get() = context.getSharedPreferencesFile(intermediateName) get() = context.getSharedPreferencesFile(intermediateName) override fun prepareBackup(file: File) { override fun prepareBackup(file: File) { val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup") val editor = mergeSharedPreferences(intermediateSharedPreferences, sharedPreferences.all, "Backup") // commit to ensure data is write to disk synchronously // commit to ensure data is write to disk synchronously if (!editor.commit()) { if (!editor.commit()) { Log.w(LOG_TAG, "[$name] fail to commit") Log.w(LOG_TAG, "[$name] fail to commit") Loading @@ -104,8 +120,8 @@ constructor( // observers consistently once restore finished. // observers consistently once restore finished. sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) val restored = intermediateSharedPreferences val restored = intermediateSharedPreferences val editor = sharedPreferences.merge(restored.all, "Restore") val editor = mergeSharedPreferences(sharedPreferences, restored.all, "Restore") editor.apply() // apply to avoid blocking editor.commit() // commit to avoid race condition sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener) // clear the intermediate SharedPreferences // clear the intermediate SharedPreferences restored.delete(intermediateName) restored.delete(intermediateName) Loading @@ -115,7 +131,7 @@ constructor( if (deleteSharedPreferences(name)) { if (deleteSharedPreferences(name)) { Log.i(LOG_TAG, "SharedPreferences $name deleted") Log.i(LOG_TAG, "SharedPreferences $name deleted") } else { } else { edit().clear().apply() edit().clear().commit() // commit to avoid potential race condition } } } } Loading @@ -126,11 +142,13 @@ constructor( false false } } private fun SharedPreferences.merge( @VisibleForTesting internal open fun mergeSharedPreferences( sharedPreferences: SharedPreferences, entries: Map<String, Any?>, entries: Map<String, Any?>, operation: String operation: String, ): SharedPreferences.Editor { ): SharedPreferences.Editor { val editor = edit() val editor = sharedPreferences.edit() for ((key, value) in entries) { for ((key, value) in entries) { if (!filter.invoke(key, value)) { if (!filter.invoke(key, value)) { if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value") if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value") Loading Loading @@ -184,7 +202,7 @@ constructor( companion object { companion object { private fun Context.getSharedPreferencesFilePath(name: String): String { private fun Context.getSharedPreferencesFilePath(name: String): String { val file = getSharedPreferencesFile(name) val file = getSharedPreferencesFile(name) return file.relativeTo(ContextCompat.getDataDir(this)!!).toString() return file.relativeTo(dataDirCompat).toString() } } /** Returns the absolute path of shared preferences file. */ /** Returns the absolute path of shared preferences file. */ Loading