fix: 首次提交

This commit is contained in:
2024-12-09 11:25:23 +08:00
parent d0c01071e9
commit 2c2109a5f3
4741 changed files with 290641 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
package {
default_applicable_licenses: ["frameworks_base_license"],
}
android_library {
name: "SettingsLibDataStore",
defaults: [
"SettingsLintDefaults",
],
srcs: ["src/**/*"],
static_libs: [
"androidx.annotation_annotation",
"androidx.collection_collection-ktx",
"guava",
],
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.datastore">
<uses-sdk android:minSdkVersion="21" />
</manifest>

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import android.app.backup.BackupAgent
import android.app.backup.BackupDataOutput
import android.app.backup.BackupHelper
import android.os.Build
import android.os.ParcelFileDescriptor
import androidx.annotation.RequiresApi
/**
* Context for backup.
*
* @see BackupHelper.performBackup
* @see BackupDataOutput
*/
class BackupContext
internal constructor(
/**
* An open, read-only file descriptor pointing to the last backup state provided by the
* application. May be null, in which case no prior state is being provided and the application
* should perform a full backup.
*
* TODO: the state should support marshall/unmarshall for incremental back up.
*/
val oldState: ParcelFileDescriptor?,
/** An open, read/write BackupDataOutput pointing to the backup data destination. */
private val data: BackupDataOutput,
/**
* An open, read/write file descriptor pointing to an empty file. The application should record
* the final backup.
*/
val newState: ParcelFileDescriptor,
) {
/**
* The quota in bytes for the application's current backup operation.
*
* @see [BackupDataOutput.getQuota]
*/
val quota: Long
@RequiresApi(Build.VERSION_CODES.O) get() = data.quota
/**
* Additional information about the backup transport.
*
* See [BackupAgent] for supported flags.
*
* @see [BackupDataOutput.getTransportFlags]
*/
val transportFlags: Int
@RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags
}
/** Context for restore. */
class RestoreContext(val key: String)

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import android.app.backup.BackupDataOutput
import android.app.backup.BackupHelper
import androidx.annotation.BinderThread
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
/** Entity for back up and restore. */
interface BackupRestoreEntity {
/**
* Key of the entity.
*
* The key string must be unique within the data set. Note that it is invalid if the first
* character is \uFF00 or higher.
*
* @see BackupDataOutput.writeEntityHeader
*/
val key: String
/**
* Backs up the entity.
*
* @param backupContext context for backup
* @param outputStream output stream to back up data
* @return false if backup file is deleted, otherwise true
*/
@BinderThread
@Throws(IOException::class)
fun backup(backupContext: BackupContext, outputStream: OutputStream): EntityBackupResult
/**
* Restores the entity.
*
* @param restoreContext context for restore
* @param inputStream An open input stream from which the backup data can be read.
* @see BackupHelper.restoreEntity
*/
@BinderThread
@Throws(IOException::class)
fun restore(restoreContext: RestoreContext, inputStream: InputStream)
}
/** Result of the backup operation. */
enum class EntityBackupResult {
/** Update the entity. */
UPDATE,
/** Leave the entity intact. */
INTACT,
/** Delete the entity. */
DELETE,
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import android.app.backup.BackupAgentHelper
import android.app.backup.BackupDataInputStream
import android.app.backup.BackupDataOutput
import android.app.backup.BackupHelper
import android.os.ParcelFileDescriptor
import android.util.Log
import com.google.common.io.ByteStreams
import java.io.ByteArrayOutputStream
import java.io.FilterInputStream
import java.io.InputStream
import java.io.OutputStream
internal const val LOG_TAG = "BackupRestoreStorage"
/**
* Storage with backup and restore support. Subclass must implement either [Observable] or
* [KeyedObservable] interface.
*
* The storage is identified by a unique string [name] and data set is split into entities
* ([BackupRestoreEntity]).
*/
abstract class BackupRestoreStorage : BackupHelper {
/**
* A unique string used to disambiguate the various storages within backup agent.
*
* It will be used as the `keyPrefix` of [BackupAgentHelper.addHelper].
*/
abstract val name: String
private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }
/** Entities to back up and restore. */
abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
override fun performBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput,
newState: ParcelFileDescriptor,
) {
val backupContext = BackupContext(oldState, data, newState)
if (!enableBackup(backupContext)) {
Log.i(LOG_TAG, "[$name] Backup disabled")
return
}
Log.i(LOG_TAG, "[$name] Backup start")
for (entity in entities) {
val key = entity.key
val outputStream = ByteArrayOutputStream()
val result =
try {
entity.backup(backupContext, wrapBackupOutputStream(outputStream))
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception)
continue
}
when (result) {
EntityBackupResult.UPDATE -> {
val payload = outputStream.toByteArray()
val size = payload.size
data.writeEntityHeader(key, size)
data.writeEntityData(payload, size)
Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
}
EntityBackupResult.INTACT -> {
Log.i(LOG_TAG, "[$name] Backup entity $key intact")
}
EntityBackupResult.DELETE -> {
data.writeEntityHeader(key, -1)
Log.i(LOG_TAG, "[$name] Backup entity $key deleted")
}
}
}
Log.i(LOG_TAG, "[$name] Backup end")
}
/** Returns if backup is enabled. */
open fun enableBackup(backupContext: BackupContext): Boolean = true
fun wrapBackupOutputStream(outputStream: OutputStream): OutputStream {
return outputStream
}
override fun restoreEntity(data: BackupDataInputStream) {
val key = data.key
if (!enableRestore()) {
Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key")
return
}
val entity = entities.firstOrNull { it.key == key }
if (entity == null) {
Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key")
return
}
Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes")
val restoreContext = RestoreContext(key)
try {
entity.restore(restoreContext, wrapRestoreInputStream(data))
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception)
}
}
/** Returns if restore is enabled. */
open fun enableRestore(): Boolean = true
fun wrapRestoreInputStream(inputStream: BackupDataInputStream): InputStream {
return LimitedNoCloseInputStream(inputStream)
}
override fun writeNewStateDescription(newState: ParcelFileDescriptor) {}
}
/**
* Wrapper of [BackupDataInputStream], limiting the number of bytes that can be read and make
* [close] no-op.
*/
class LimitedNoCloseInputStream(inputStream: BackupDataInputStream) :
FilterInputStream(ByteStreams.limit(inputStream, inputStream.size().toLong())) {
override fun close() {
// do not close original input stream
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import android.app.Application
import android.app.backup.BackupAgentHelper
import android.app.backup.BackupManager
import android.content.Context
import android.util.Log
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.ConcurrentHashMap
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
private val executor = MoreExecutors.directExecutor()
private val observer = Observer { reason -> notifyBackupManager(null, reason) }
private val keyedObserver =
KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
private fun notifyBackupManager(key: Any?, reason: Int) {
// prefer not triggering backup immediately after restore
if (reason == ChangeReason.RESTORE) return
// TODO: log storage name
Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
BackupManager.dataChanged(application.packageName)
}
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
* @see BackupAgentHelper.addHelper
*/
fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
for ((keyPrefix, storage) in storages) {
backupAgentHelper.addHelper(keyPrefix, storage)
}
}
/**
* Callback when restore finished.
*
* The observers of the storages will be notified.
*/
fun onRestoreFinished() {
for (storage in storages.values) {
storage.notifyRestoreFinished()
}
}
private fun BackupRestoreStorage.notifyRestoreFinished() {
when (this) {
is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
is Observable -> notifyChange(ChangeReason.RESTORE)
}
}
/**
* Adds a list of storages.
*
* The storage MUST implement [KeyedObservable] or [Observable].
*/
fun add(vararg storages: BackupRestoreStorage) {
for (storage in storages) add(storage)
}
/**
* Adds a storage.
*
* The storage MUST implement [KeyedObservable] or [Observable].
*/
fun add(storage: BackupRestoreStorage) {
val name = storage.name
val oldStorage = storages.put(name, storage)
if (oldStorage != null) {
throw IllegalStateException(
"Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
)
}
storage.addObserver()
}
private fun BackupRestoreStorage.addObserver() {
when (this) {
is KeyedObservable<*> -> addObserver(keyedObserver, executor)
is Observable -> addObserver(observer, executor)
else ->
throw IllegalArgumentException(
"$this does not implement either KeyedObservable or Observable"
)
}
}
/** Removes all the storages. */
fun removeAll() {
for ((name, _) in storages) remove(name)
}
/** Removes storage with given name. */
fun remove(name: String): BackupRestoreStorage? {
val storage = storages.remove(name)
storage?.removeObserver()
return storage
}
private fun BackupRestoreStorage.removeObserver() {
when (this) {
is KeyedObservable<*> -> removeObserver(keyedObserver)
is Observable -> removeObserver(observer)
}
}
/** Returns storage with given name. */
fun get(name: String): BackupRestoreStorage? = storages[name]
/** Returns storage with given name, exception is raised if not found. */
fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
companion object {
@Volatile private var instance: BackupRestoreStorageManager? = null
/** Returns the singleton of manager. */
@JvmStatic
fun getInstance(context: Context): BackupRestoreStorageManager {
val result = instance
if (result != null) return result
synchronized(this) {
if (instance == null) {
instance =
BackupRestoreStorageManager(context.applicationContext as Application)
}
}
return instance!!
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import androidx.annotation.AnyThread
import androidx.annotation.GuardedBy
import androidx.collection.MutableScatterMap
import java.util.WeakHashMap
import java.util.concurrent.Executor
/**
* Callback to be informed of changes in [KeyedObservable] object.
*
* The observer is weakly referenced, a strong reference must be kept.
*/
fun interface KeyedObserver<in K> {
/**
* Called by [KeyedObservable] in the event of changes.
*
* This callback will run in the given [Executor] when observer is added.
*
* @param key key that has been changed
* @param reason the reason of change
* @see KeyedObservable.addObserver
*/
fun onKeyChanged(key: K, @ChangeReason reason: Int)
}
/**
* A key-value observable object allows to observe change with [KeyedObserver].
*
* Notes:
* - The order in which observers will be notified is unspecified.
* - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong
* reference of the observer.
* - It is possible that the callback may be triggered even there is no real data change. For
* example, when data restore/clear happens, it might be too complex to check if data is really
* changed, thus all the registered observers are notified directly.
*/
@AnyThread
interface KeyedObservable<K> {
/**
* Adds an observer for any key.
*
* The observer will be notified whenever a change happens. The [KeyedObserver.onKeyChanged]
* callback will be invoked with specific key that is modified. However, `null` key is passed in
* the cases that a bunch of keys are changed simultaneously (e.g. clear data, restore happens).
*
* @param observer observer to be notified
* @param executor executor to run the callback
*/
fun addObserver(observer: KeyedObserver<K?>, executor: Executor)
/**
* Adds an observer on given key.
*
* The observer will be notified only when the given key is changed.
*
* @param key key to observe
* @param observer observer to be notified
* @param executor executor to run the callback
*/
fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor)
/** Removes observer. */
fun removeObserver(observer: KeyedObserver<K?>)
/** Removes observer on given key. */
fun removeObserver(key: K, observer: KeyedObserver<K>)
/**
* Notifies all observers that a change occurs.
*
* All the any key and keyed observers are notified.
*
* @param reason reason of the change
*/
fun notifyChange(@ChangeReason reason: Int)
/**
* Notifies observers that a change occurs on given key.
*
* The any key and specific key observers are notified.
*
* @param key key of the change
* @param reason reason of the change
*/
fun notifyChange(key: K, @ChangeReason reason: Int)
}
/** A thread safe implementation of [KeyedObservable]. */
class KeyedDataObservable<K> : KeyedObservable<K> {
// Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be
// synchronized outside by the holder
@GuardedBy("itself") private val observers = WeakHashMap<KeyedObserver<K?>, Executor>()
@GuardedBy("itself")
private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>()
override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) {
val oldExecutor = synchronized(observers) { observers.put(observer, executor) }
if (oldExecutor != null && oldExecutor != executor) {
throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
}
}
override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) {
val oldExecutor =
synchronized(keyedObservers) {
keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor)
}
if (oldExecutor != null && oldExecutor != executor) {
throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
}
}
override fun removeObserver(observer: KeyedObserver<K?>) {
synchronized(observers) { observers.remove(observer) }
}
override fun removeObserver(key: K, observer: KeyedObserver<K>) {
synchronized(keyedObservers) {
val observers = keyedObservers[key]
if (observers?.remove(observer) != null && observers.isEmpty()) {
keyedObservers.remove(key)
}
}
}
override fun notifyChange(@ChangeReason reason: Int) {
// make a copy to avoid potential ConcurrentModificationException
val observers = synchronized(observers) { observers.entries.toTypedArray() }
val keyedObservers = synchronized(keyedObservers) { keyedObservers.copy() }
for (entry in observers) {
val observer = entry.key // avoid reference "entry"
entry.value.execute { observer.onKeyChanged(null, reason) }
}
for (pair in keyedObservers) {
val key = pair.first
for (entry in pair.second) {
val observer = entry.key // avoid reference "entry"
entry.value.execute { observer.onKeyChanged(key, reason) }
}
}
}
private fun MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>.copy():
List<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>> {
val result = ArrayList<Pair<K, Array<Map.Entry<KeyedObserver<K>, Executor>>>>(size)
forEach { key, value -> result.add(Pair(key, value.entries.toTypedArray())) }
return result
}
override fun notifyChange(key: K, @ChangeReason reason: Int) {
// make a copy to avoid potential ConcurrentModificationException
val observers = synchronized(observers) { observers.entries.toTypedArray() }
val keyedObservers =
synchronized(keyedObservers) { keyedObservers[key]?.entries?.toTypedArray() }
?: arrayOf()
for (entry in observers) {
val observer = entry.key // avoid reference "entry"
entry.value.execute { observer.onKeyChanged(key, reason) }
}
for (entry in keyedObservers) {
val observer = entry.key // avoid reference "entry"
entry.value.execute { observer.onKeyChanged(key, reason) }
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
/**
* A [BackupRestoreStorage] that implements [Observable].
*
* This class provides the [Observable] implementations on top of [DataObservable] by delegation.
*/
abstract class ObservableBackupRestoreStorage :
BackupRestoreStorage(), Observable by DataObservable()

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2024 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 com.android.settingslib.datastore
import androidx.annotation.AnyThread
import androidx.annotation.GuardedBy
import androidx.annotation.IntDef
import java.util.WeakHashMap
import java.util.concurrent.Executor
/** The reason of a change. */
@IntDef(
ChangeReason.UNKNOWN,
ChangeReason.UPDATE,
ChangeReason.DELETE,
ChangeReason.RESTORE,
ChangeReason.SYNC_ACROSS_PROFILES,
)
@Retention(AnnotationRetention.SOURCE)
annotation class ChangeReason {
companion object {
/** Unknown reason of the change. */
const val UNKNOWN = 0
/** Data is updated. */
const val UPDATE = 1
/** Data is deleted. */
const val DELETE = 2
/** Data is restored from backup/restore framework. */
const val RESTORE = 3
/** Data is synced from another profile (e.g. personal profile to work profile). */
const val SYNC_ACROSS_PROFILES = 4
}
}
/**
* Callback to be informed of changes in [Observable] object.
*
* The observer is weakly referenced, a strong reference must be kept.
*/
fun interface Observer {
/**
* Called by [Observable] in the event of changes.
*
* This callback will run in the given [Executor] when observer is added.
*
* @param reason the reason of change
* @see [Observable.addObserver] for the notices.
*/
fun onChanged(@ChangeReason reason: Int)
}
/** An observable object allows to observe change with [Observer]. */
@AnyThread
interface Observable {
/**
* Adds an observer.
*
* Notes:
* - The order in which observers will be notified is unspecified.
* - The observer is weakly referenced to avoid memory leaking, the call site must keep a strong
* reference of the observer.
* - It is possible that the callback may be triggered even there is no real data change. For
* example, when data restore/clear happens, it might be too complex to check if data is
* really changed, thus all the registered observers are notified directly.
*
* @param observer observer to be notified
* @param executor executor to run the [Observer.onChanged] callback
*/
fun addObserver(observer: Observer, executor: Executor)
/** Removes given observer. */
fun removeObserver(observer: Observer)
/**
* Notifies observers that a change occurs.
*
* @param reason reason of the change
*/
fun notifyChange(@ChangeReason reason: Int)
}
/** A thread safe implementation of [Observable]. */
class DataObservable : Observable {
// Instead of @GuardedBy("this"), guarded by itself because DataObservable object could be
// synchronized outside by the holder
@GuardedBy("itself") private val observers = WeakHashMap<Observer, Executor>()
override fun addObserver(observer: Observer, executor: Executor) {
val oldExecutor = synchronized(observers) { observers.put(observer, executor) }
if (oldExecutor != null && oldExecutor != executor) {
throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor")
}
}
override fun removeObserver(observer: Observer) {
synchronized(observers) { observers.remove(observer) }
}
override fun notifyChange(@ChangeReason reason: Int) {
// make a copy to avoid potential ConcurrentModificationException
val entries = synchronized(observers) { observers.entries.toTypedArray() }
for (entry in entries) {
val observer = entry.key // avoid reference "entry"
entry.value.execute { observer.onChanged(reason) }
}
}
}