Skip to content

Commit

Permalink
Simplify transport init and token handling
Browse files Browse the repository at this point in the history
The token used to be very important, because it was our restore set folder name. Now it is just a number in a snapshot, so things get a bit simpler.
  • Loading branch information
grote committed Sep 11, 2024
1 parent 0c64482 commit b880a87
Show file tree
Hide file tree
Showing 12 changed files with 32 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class PluginTest : KoinComponent {
assertEquals(0, backend.getAvailableBackupFileHandles().toList().size)

// prepare returned tokens requested when initializing device
every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1)
every { mockedSettingsManager.token } returnsMany listOf(token, token + 1, token + 1)

// write metadata (needed for backup to be recognized)
backend.save(LegacyAppBackupFile.Metadata(token))
Expand All @@ -117,7 +117,7 @@ class PluginTest : KoinComponent {

@Test
fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) {
every { mockedSettingsManager.getToken() } returns token
every { mockedSettingsManager.token } returns token

// write metadata
val metadata = getRandomByteArray()
Expand Down Expand Up @@ -201,7 +201,7 @@ class PluginTest : KoinComponent {
}

private fun initStorage(token: Long) = runBlocking {
every { mockedSettingsManager.getToken() } returns token
every { mockedSettingsManager.token } returns token
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ internal interface LargeTestBase : KoinComponent {

fun resetApplicationState() {
backupManager.setAutoRestore(false)
settingsManager.setNewToken(null)
settingsManager.token = null

val sharedPreferences = permitDiskReads {
PreferenceManager.getDefaultSharedPreferences(targetContext)
Expand Down
17 changes: 0 additions & 17 deletions app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ open class App : Application() {
.build()
)
}
permitDiskReads {
migrateTokenFromMetadataToSettingsManager()
}
if (!isTest) migrateToOwnScheduling()
}

Expand Down Expand Up @@ -158,20 +155,6 @@ open class App : Application() {
private val backendManager: BackendManager by inject()
private val backupStateManager: BackupStateManager by inject()

/**
* The responsibility for the current token was moved to the [SettingsManager]
* in the end of 2020.
* This method migrates the token for existing installs and can be removed
* after sufficient time has passed.
*/
private fun migrateTokenFromMetadataToSettingsManager() {
@Suppress("DEPRECATION")
val token = metadataManager.getBackupToken()
if (token != 0L && settingsManager.getToken() == null) {
settingsManager.setNewToken(token)
}
}

/**
* Disables the framework scheduling in favor of our own.
* Introduced in the first half of 2024 and can be removed after a suitable migration period.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ package com.stevesoltys.seedvault.metadata
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.pm.PackageInfo
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.UserManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.encodeBase64
import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.settings.SettingsManager
Expand All @@ -39,14 +34,13 @@ internal const val METADATA_SALT_SIZE = 32
internal class MetadataManager(
private val context: Context,
private val clock: Clock,
private val crypto: Crypto,
private val metadataWriter: MetadataWriter,
private val metadataReader: MetadataReader,
private val packageService: PackageService,
private val settingsManager: SettingsManager,
) {

private val uninitializedMetadata = BackupMetadata(token = 0L, salt = "")
private val uninitializedMetadata = BackupMetadata(token = -42L, salt = "foo bar")
private var metadata: BackupMetadata = uninitializedMetadata
get() {
if (field == uninitializedMetadata) {
Expand All @@ -65,37 +59,10 @@ internal class MetadataManager(
return field
}

val backupSize: Long get() = metadata.size

private val launchableSystemApps by lazy {
packageService.launchableSystemApps.map { it.activityInfo.packageName }.toSet()
}

/**
* Call this when initializing a new device.
*
* Existing [BackupMetadata] will be cleared
* and new metadata with the given [token] will be written to the internal cache
* with a fresh salt.
*/
@Synchronized
@Throws(IOException::class)
fun onDeviceInitialization(token: Long) {
val salt = crypto.getRandomBytes(METADATA_SALT_SIZE).encodeBase64()
modifyCachedMetadata {
val userName = getUserName()
metadata = BackupMetadata(
token = token,
salt = salt,
deviceName = if (userName == null) {
"${Build.MANUFACTURER} ${Build.MODEL}"
} else {
"${Build.MANUFACTURER} ${Build.MODEL} - $userName"
},
)
}
}

/**
* Call this after a package's APK has been backed up successfully.
*
Expand Down Expand Up @@ -264,18 +231,6 @@ internal class MetadataManager(
mLastBackupTime.postValue(metadata.time) // TODO only do after snapshot was written
}

/**
* Returns the current backup token.
*
* If the token is 0L, it is not yet initialized and must not be used for anything.
*/
@Synchronized
@Deprecated(
"Responsibility for current token moved to SettingsManager",
ReplaceWith("settingsManager.getToken()")
)
fun getBackupToken(): Long = metadata.token

/**
* Returns the last backup time in unix epoch milli seconds.
*
Expand Down Expand Up @@ -328,12 +283,4 @@ internal class MetadataManager(
}
}

private fun getUserName(): String? {
val perm = "android.permission.QUERY_USERS"
return if (context.checkSelfPermission(perm) == PERMISSION_GRANTED) {
val userManager = context.getSystemService(UserManager::class.java) ?: return null
userManager.userName
} else null
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

val metadataModule = module {
single { MetadataManager(androidContext(), get(), get(), get(), get(), get(), get()) }
single { MetadataManager(androidContext(), get(), get(), get(), get(), get()) }
single<MetadataWriter> { MetadataWriterImpl(get()) }
single<MetadataReader> { MetadataReaderImpl(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,6 @@ internal class AppDataRestoreManager(

Log.d(TAG, "Starting new restore session to restore backup $token")

// if we had no token before (i.e. restore from setup wizard),
// use the token of the current restore set from now on
if (settingsManager.getToken() == null) {
settingsManager.setNewToken(token)
}

// start a new restore session
val session = try {
getOrStartSession()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import androidx.annotation.UiThread
import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.backend.webdav.WebDavHandler.Companion.createWebDavProperties
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.saf.SafBackend
import org.calyxos.seedvault.core.backends.saf.SafProperties
Expand Down Expand Up @@ -63,9 +62,6 @@ class SettingsManager(private val context: Context) {
PreferenceManager.getDefaultSharedPreferences(context)
}

@Volatile
private var token: Long? = null

fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
prefs.registerOnSharedPreferenceChangeListener(listener)
}
Expand All @@ -83,29 +79,26 @@ class SettingsManager(private val context: Context) {
ConcurrentSkipListSet(prefs.getStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, emptySet()))
}

fun getToken(): Long? = token ?: run {
val value = prefs.getLong(PREF_KEY_TOKEN, 0L)
if (value == 0L) null else value
}

/**
* Sets a new RestoreSet token.
* Should only be called by the [BackupCoordinator]
* to ensure that related work is performed after moving to a new token.
*/
fun setNewToken(newToken: Long?) {
if (newToken == null) {
prefs.edit()
.remove(PREF_KEY_TOKEN)
.apply()
} else {
prefs.edit()
.putLong(PREF_KEY_TOKEN, newToken)
.apply()
@Volatile
var token: Long? = null
set(newToken) {
if (newToken == null) {
prefs.edit()
.remove(PREF_KEY_TOKEN)
.apply()
} else {
prefs.edit()
.putLong(PREF_KEY_TOKEN, newToken)
.apply()
}
field = newToken
}
// we may be able to get this from latest snapshot,
// but that is not always readily available
get() = field ?: run {
val value = prefs.getLong(PREF_KEY_TOKEN, 0L)
if (value == 0L) null else value
}

token = newToken
}

internal val storagePluginType: StoragePluginType?
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import android.app.backup.BackupTransport.TRANSPORT_NOT_INITIALIZED
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
import android.app.backup.RestoreSet
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
Expand Down Expand Up @@ -84,22 +83,6 @@ internal class BackupCoordinator(
// Transport initialization and quota
//

/**
* Starts a new [RestoreSet] with a new token (the current unix epoch in milliseconds).
* Call this at least once before calling [initializeDevice]
* which must be called after this method to properly initialize the backup transport.
*
* @return the token of the new [RestoreSet].
*/
@Throws(IOException::class)
private suspend fun startNewRestoreSet() {
val token = clock.time()
Log.i(TAG, "Starting new RestoreSet with token $token...")
settingsManager.setNewToken(token)
Log.d(TAG, "Resetting backup metadata...")
metadataManager.onDeviceInitialization(token)
}

/**
* Initialize the storage for this device, erasing all stored data.
* The transport may send the request immediately, or may buffer it.
Expand All @@ -118,20 +101,15 @@ internal class BackupCoordinator(
* @return One of [TRANSPORT_OK] (OK so far) or
* [TRANSPORT_ERROR] (to retry following network error or other failure).
*/
suspend fun initializeDevice(): Int = try {
fun initializeDevice(): Int {
// we don't respect the intended system behavior here by always starting a new [RestoreSet]
// instead of simply deleting the current one
startNewRestoreSet()
Log.i(TAG, "Initialize Device!")

// [finishBackup] will only be called when we return [TRANSPORT_OK] here
// so we remember that we initialized successfully
state.calledInitialize = true
TRANSPORT_OK
} catch (e: Exception) {
Log.e(TAG, "Error initializing device", e)
// Show error notification if we needed init or were ready for backups
if (metadataManager.requiresInit || backendManager.canDoBackupNow()) nm.onBackupError()
TRANSPORT_ERROR
return TRANSPORT_OK
}

fun isAppEligibleForBackup(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ internal class RestoreCoordinator(
* or 0 if there is no backup set available corresponding to the current device state.
*/
fun getCurrentRestoreSet(): Long {
Log.d(TAG, "getCurrentRestoreSet() = ") // TODO where to store current token?
return (settingsManager.getToken() ?: 0L).apply {
Log.i(TAG, "Got current restore set token: $this")
}
val token = settingsManager.token ?: 0L
Log.d(TAG, "getCurrentRestoreSet() = $token")
return token
}

/**
Expand Down
Loading

0 comments on commit b880a87

Please sign in to comment.