Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 095484df authored by Jacky Wang's avatar Jacky Wang Committed by Cherrypicker Worker
Browse files

[DataStore] Support backup data state computation

Entity data is computed with CRC32 checksum and saved to state file automatically.

Bug: 325144964
Test: Manual tests
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3455f82a21f8d6ce68641748149f2cdd38fa837b)
Merged-In: Ib74178fa87c1a11c39a703f6be409b33da00a00e
Change-Id: Ib74178fa87c1a11c39a703f6be409b33da00a00e
parent 6cc3696a
Loading
Loading
Loading
Loading
+6 −18
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import android.app.backup.BackupAgent
import android.app.backup.BackupDataOutput
import android.app.backup.BackupDataOutput
import android.app.backup.BackupHelper
import android.app.backup.BackupHelper
import android.os.Build
import android.os.Build
import android.os.ParcelFileDescriptor
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresApi


/**
/**
@@ -31,23 +30,8 @@ import androidx.annotation.RequiresApi
 */
 */
class BackupContext
class BackupContext
internal constructor(
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. */
    /** An open, read/write BackupDataOutput pointing to the backup data destination. */
    private val data: BackupDataOutput,
    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.
     * The quota in bytes for the application's current backup operation.
@@ -68,5 +52,9 @@ internal constructor(
        @RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags
        @RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags
}
}


/** Context for restore. */
/**
class RestoreContext(val key: String)
 * Context for restore.
 *
 * @param key Entity key
 */
class RestoreContext internal constructor(val key: String)
+9 −5
Original line number Original line Diff line number Diff line
@@ -18,11 +18,11 @@ 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.os.ParcelFileDescriptor
import android.util.Log
import android.util.Log
import java.io.File
import java.io.File
import java.io.InputStream
import java.io.InputStream
import java.io.OutputStream
import java.io.OutputStream
import java.util.zip.CheckedInputStream


/**
/**
 * File archiver to handle backup and restore for all the [BackupRestoreFileStorage] subclasses.
 * File archiver to handle backup and restore for all the [BackupRestoreFileStorage] subclasses.
@@ -62,8 +62,10 @@ internal class BackupRestoreFileArchiver(
            }
            }
        Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
        Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
        val inputStream = LimitedNoCloseInputStream(data)
        val inputStream = LimitedNoCloseInputStream(data)
        checksum.reset()
        val checkedInputStream = CheckedInputStream(inputStream, checksum)
        try {
        try {
            val codec = BackupCodec.fromId(inputStream.read().toByte())
            val codec = BackupCodec.fromId(checkedInputStream.read().toByte())
            if (fileStorage != null && fileStorage.defaultCodec().id != codec.id) {
            if (fileStorage != null && fileStorage.defaultCodec().id != codec.id) {
                Log.i(
                Log.i(
                    LOG_TAG,
                    LOG_TAG,
@@ -71,17 +73,19 @@ internal class BackupRestoreFileArchiver(
                )
                )
            }
            }
            file.parentFile?.mkdirs() // ensure parent folders are created
            file.parentFile?.mkdirs() // ensure parent folders are created
            val wrappedInputStream = codec.decode(inputStream)
            val wrappedInputStream = codec.decode(checkedInputStream)
            val bytesCopied = file.outputStream().use { wrappedInputStream.copyTo(it) }
            val bytesCopied = file.outputStream().use { wrappedInputStream.copyTo(it) }
            Log.i(LOG_TAG, "[$name] $key restore $bytesCopied bytes with ${codec.name}")
            Log.i(LOG_TAG, "[$name] $key restore $bytesCopied bytes with ${codec.name}")
            fileStorage?.onRestoreFinished(file)
            fileStorage?.onRestoreFinished(file)
            entityStates[key] = checksum.value
        } catch (e: Exception) {
        } catch (e: Exception) {
            Log.e(LOG_TAG, "[$name] Fail to restore $key", e)
            Log.e(LOG_TAG, "[$name] Fail to restore $key", e)
        }
        }
    }
    }


    override fun writeNewStateDescription(newState: ParcelFileDescriptor) =
    override fun onRestoreFinished() {
        fileStorages.forEach { it.writeNewStateDescription(newState) }
        fileStorages.forEach { it.onRestoreFinished() }
    }
}
}


private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
+110 −12
Original line number Original line Diff line number Diff line
@@ -22,11 +22,20 @@ 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.collection.MutableScatterMap
import com.google.common.io.ByteStreams
import com.google.common.io.ByteStreams
import java.io.ByteArrayOutputStream
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.EOFException
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.FilterInputStream
import java.io.FilterInputStream
import java.io.InputStream
import java.io.InputStream
import java.io.OutputStream
import java.io.OutputStream
import java.util.zip.CRC32
import java.util.zip.CheckedInputStream
import java.util.zip.CheckedOutputStream


internal const val LOG_TAG = "BackupRestoreStorage"
internal const val LOG_TAG = "BackupRestoreStorage"


@@ -47,6 +56,20 @@ abstract class BackupRestoreStorage : BackupHelper {


    private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }
    private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }


    /**
     * Checksum of the data.
     *
     * Always call [java.util.zip.Checksum.reset] before using it.
     */
    protected val checksum = CRC32()

    /**
     * Entity states represented by checksum.
     *
     * Map key is the entity key, map value is the checksum of backup data.
     */
    protected val entityStates = MutableScatterMap<String, Long>()

    /** Entities to back up and restore. */
    /** Entities to back up and restore. */
    abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
    abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>


@@ -58,7 +81,8 @@ abstract class BackupRestoreStorage : BackupHelper {
        data: BackupDataOutput,
        data: BackupDataOutput,
        newState: ParcelFileDescriptor,
        newState: ParcelFileDescriptor,
    ) {
    ) {
        val backupContext = BackupContext(oldState, data, newState)
        oldState.readEntityStates(entityStates)
        val backupContext = BackupContext(data)
        if (!enableBackup(backupContext)) {
        if (!enableBackup(backupContext)) {
            Log.i(LOG_TAG, "[$name] Backup disabled")
            Log.i(LOG_TAG, "[$name] Backup disabled")
            return
            return
@@ -67,34 +91,50 @@ abstract class BackupRestoreStorage : BackupHelper {
        for (entity in entities) {
        for (entity in entities) {
            val key = entity.key
            val key = entity.key
            val outputStream = ByteArrayOutputStream()
            val outputStream = ByteArrayOutputStream()
            checksum.reset()
            val checkedOutputStream = CheckedOutputStream(outputStream, checksum)
            val codec = entity.codec() ?: defaultCodec()
            val codec = entity.codec() ?: defaultCodec()
            val result =
            val result =
                try {
                try {
                    entity.backup(backupContext, wrapBackupOutputStream(codec, outputStream))
                    entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream))
                } 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
                }
                }
            when (result) {
            when (result) {
                EntityBackupResult.UPDATE -> {
                EntityBackupResult.UPDATE -> {
                    if (updateEntityState(key)) {
                        val payload = outputStream.toByteArray()
                        val payload = outputStream.toByteArray()
                        val size = payload.size
                        val size = payload.size
                        data.writeEntityHeader(key, size)
                        data.writeEntityHeader(key, size)
                        data.writeEntityData(payload, size)
                        data.writeEntityData(payload, size)
                        Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
                        Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
                    } else {
                        Log.i(
                            LOG_TAG,
                            "[$name] Backup entity $key unchanged: ${outputStream.size()} bytes"
                        )
                    }
                }
                }
                EntityBackupResult.INTACT -> {
                EntityBackupResult.INTACT -> {
                    Log.i(LOG_TAG, "[$name] Backup entity $key intact")
                    Log.i(LOG_TAG, "[$name] Backup entity $key intact")
                }
                }
                EntityBackupResult.DELETE -> {
                EntityBackupResult.DELETE -> {
                    entityStates.remove(key)
                    data.writeEntityHeader(key, -1)
                    data.writeEntityHeader(key, -1)
                    Log.i(LOG_TAG, "[$name] Backup entity $key deleted")
                    Log.i(LOG_TAG, "[$name] Backup entity $key deleted")
                }
                }
            }
            }
        }
        }
        newState.writeEntityStates(entityStates)
        Log.i(LOG_TAG, "[$name] Backup end")
        Log.i(LOG_TAG, "[$name] Backup end")
    }
    }


    private fun updateEntityState(key: String): Boolean {
        val value = checksum.value
        return entityStates.put(key, value) != value
    }

    /** Returns if backup is enabled. */
    /** Returns if backup is enabled. */
    open fun enableBackup(backupContext: BackupContext): Boolean = true
    open fun enableBackup(backupContext: BackupContext): Boolean = true


@@ -118,11 +158,12 @@ abstract class BackupRestoreStorage : BackupHelper {
        Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes")
        Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes")
        val restoreContext = RestoreContext(key)
        val restoreContext = RestoreContext(key)
        val codec = entity.codec() ?: defaultCodec()
        val codec = entity.codec() ?: defaultCodec()
        val inputStream = LimitedNoCloseInputStream(data)
        checksum.reset()
        val checkedInputStream = CheckedInputStream(inputStream, checksum)
        try {
        try {
            entity.restore(
            entity.restore(restoreContext, wrapRestoreInputStream(codec, checkedInputStream))
                restoreContext,
            entityStates[key] = checksum.value
                wrapRestoreInputStream(codec, LimitedNoCloseInputStream(data))
            )
        } catch (exception: Exception) {
        } catch (exception: Exception) {
            Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception)
            Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception)
        }
        }
@@ -143,7 +184,64 @@ abstract class BackupRestoreStorage : BackupHelper {
        return BackupCodec.fromId(id.toByte()).decode(inputStream)
        return BackupCodec.fromId(id.toByte()).decode(inputStream)
    }
    }


    override fun writeNewStateDescription(newState: ParcelFileDescriptor) {}
    final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
        newState.writeEntityStates(entityStates)
        onRestoreFinished()
    }

    /** Callbacks when restore finished. */
    open fun onRestoreFinished() {}

    private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) {
        state.clear()
        if (this == null) return
        // do not close the streams
        val fileInputStream = FileInputStream(fileDescriptor)
        val dataInputStream = DataInputStream(fileInputStream)
        try {
            val version = dataInputStream.readByte()
            if (version != STATE_VERSION) {
                Log.w(
                    LOG_TAG,
                    "[$name] Unexpected state version, read:$version, expected:$STATE_VERSION"
                )
                return
            }
            var count = dataInputStream.readInt()
            while (count-- > 0) {
                val key = dataInputStream.readUTF()
                val checksum = dataInputStream.readLong()
                state[key] = checksum
            }
        } catch (exception: Exception) {
            if (exception is EOFException) {
                Log.d(LOG_TAG, "[$name] Hit EOF when read state file")
            } else {
                Log.e(LOG_TAG, "[$name] Fail to read state file", exception)
            }
            state.clear()
        }
    }

    private fun ParcelFileDescriptor.writeEntityStates(state: MutableScatterMap<String, Long>) {
        // do not close the streams
        val fileOutputStream = FileOutputStream(fileDescriptor)
        val dataOutputStream = DataOutputStream(fileOutputStream)
        try {
            dataOutputStream.writeByte(STATE_VERSION.toInt())
            dataOutputStream.writeInt(state.size)
            state.forEach { key, value ->
                dataOutputStream.writeUTF(key)
                dataOutputStream.writeLong(value)
            }
        } catch (exception: Exception) {
            Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
        }
    }

    companion object {
        private const val STATE_VERSION: Byte = 0
    }
}
}


/**
/**