Address book android auto backup (#1641)

* Address book android auto backup

* Documentation update

* Address book container folder added

* Address book general error handling

* Address book memory storage hotfix

* Documentation update

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Milan 2024-11-15 15:10:17 +01:00 committed by GitHub
parent 95285c5133
commit 32b132950c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 75 additions and 41 deletions

View File

@ -8,6 +8,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
### Added
- Address book encryption
- Android auto backup support for address book encryption
- The device authentication feature on the Zashi app launch has been added
- Zashi app now supports Spanish language. It can be changed in the System settings options.
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI

View File

@ -4,7 +4,10 @@
<application
android:name="co.electriccoin.zcash.app.ZcashApplication"
android:allowBackup="false"
android:allowBackup="true"
android:fullBackupContent="@xml/auto_backup_config"
android:dataExtractionRules="@xml/auto_backup_config_android_12"
android:backupInForeground="true"
android:icon="@mipmap/ic_launcher_square"
android:roundIcon="@mipmap/ic_launcher_round"
android:localeConfig="@xml/locales_config"

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<include
domain="file"
path="address_book/." />
</full-backup-content>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup disableIfNoEncryptionCapabilities="false">
<include
domain="file"
path="address_book/." />
</cloud-backup>
</data-extraction-rules>

View File

@ -4,7 +4,6 @@
<application
android:name="co.electriccoin.zcash.app.ZcashApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher_square"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name">

View File

@ -4,7 +4,6 @@
<application
android:name="co.electriccoin.zcash.app.ZcashApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher_square"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name">

View File

@ -4,7 +4,6 @@
<application
android:name="co.electriccoin.zcash.app.ZcashApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher_square"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name">

View File

@ -1,4 +1,9 @@
# Documentation
Here you'll find documentation
TBD
# Android auto backup testing
To force the android system back up the app use the following commands:
```
adb shell bmgr enable true
adb shell bmgr backupnow co.electriccoin.zcash.debug
```

View File

@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
### Added
- Address book encryption
- Android auto backup support for address book encryption
- The device authentication feature on the Zashi app launch has been added
- Zashi app now supports Spanish language. It can be changed in the System settings options.
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI

View File

@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
### Added
- Address book encryption
- Android auto backup support for address book encryption
- The device authentication feature on the Zashi app launch has been added
- Zashi app now supports Spanish language. It can be changed in the System settings options.
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI

View File

@ -11,8 +11,6 @@ import co.electriccoin.zcash.ui.common.serialization.addressbook.AddressBookKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import java.io.IOException
import java.security.GeneralSecurityException
interface LocalAddressBookDataSource {
suspend fun getContacts(addressBookKey: AddressBookKey): AddressBook
@ -61,7 +59,9 @@ class LocalAddressBookDataSourceImpl(
lastUpdated = Clock.System.now(),
version = ADDRESS_BOOK_SERIALIZATION_V1,
contacts = emptyList(),
)
).also {
this@LocalAddressBookDataSourceImpl.addressBook = it
}
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
}
newAddressBook
@ -88,7 +88,9 @@ class LocalAddressBookDataSourceImpl(
address = address,
lastUpdated = lastUpdated,
),
)
).also {
addressBook = it
}
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
newAddressBook
}
@ -118,7 +120,9 @@ class LocalAddressBookDataSourceImpl(
)
}
.toList(),
)
).also {
addressBook = it
}
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
newAddressBook
}
@ -139,7 +143,9 @@ class LocalAddressBookDataSourceImpl(
remove(addressBookContact)
}
.toList(),
)
).also {
addressBook = it
}
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
newAddressBook
}
@ -158,22 +164,15 @@ class LocalAddressBookDataSourceImpl(
@Suppress("ReturnCount")
private suspend fun readLocalFileToAddressBook(addressBookKey: AddressBookKey): AddressBook? {
val encryptedFile = addressBookStorageProvider.getStorageFile(addressBookKey)
val unencryptedFile = addressBookStorageProvider.getLegacyUnencryptedStorageFile()
val encryptedFile = runCatching { addressBookStorageProvider.getStorageFile(addressBookKey) }.getOrNull()
val unencryptedFile = runCatching { addressBookStorageProvider.getLegacyUnencryptedStorageFile() }.getOrNull()
if (encryptedFile != null) {
return try {
addressBookProvider.readAddressBookFromFile(encryptedFile, addressBookKey)
.also {
unencryptedFile?.deleteSuspend()
}
} catch (e: GeneralSecurityException) {
Twig.warn(e) { "Failed to decrypt address book" }
null
} catch (e: IOException) {
Twig.warn(e) { "Failed to decrypt address book" }
null
}
return runCatching {
addressBookProvider
.readAddressBookFromFile(encryptedFile, addressBookKey)
.also { unencryptedFile?.deleteSuspend() }
}.onFailure { e -> Twig.warn(e) { "Failed to decrypt address book" } }.getOrNull()
}
return if (unencryptedFile != null) {
@ -191,7 +190,9 @@ class LocalAddressBookDataSourceImpl(
addressBook: AddressBook,
addressBookKey: AddressBookKey
) {
val file = addressBookStorageProvider.getOrCreateStorageFile(addressBookKey)
addressBookProvider.writeAddressBookToFile(file, addressBook, addressBookKey)
runCatching {
val file = addressBookStorageProvider.getOrCreateStorageFile(addressBookKey)
addressBookProvider.writeAddressBookToFile(file, addressBook, addressBookKey)
}.onFailure { e -> Twig.warn(e) { "Failed to write address book" } }
}
}

View File

@ -11,17 +11,17 @@ interface AddressBookStorageProvider {
fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File
/**
* Create a temporary file into which data from remote is written. This file is removed after usage.
*/
fun getOrCreateTempStorageFile(): File
// /**
// * Create a temporary file into which data from remote is written. This file is removed after usage.
// */
// fun getOrCreateTempStorageFile(): File
}
class AddressBookStorageProviderImpl(
private val context: Context
) : AddressBookStorageProvider {
override fun getStorageFile(addressBookKey: AddressBookKey): File? {
return File(context.noBackupFilesDir, addressBookKey.fileIdentifier())
return File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
.takeIf { it.exists() && it.isFile }
}
@ -31,19 +31,30 @@ class AddressBookStorageProviderImpl(
}
override fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File {
return getOrCreateFile(addressBookKey.fileIdentifier())
}
override fun getOrCreateTempStorageFile(): File = getOrCreateFile(REMOTE_ADDRESS_BOOK_FILE_NAME_LOCAL_COPY)
private fun getOrCreateFile(name: String): File {
val file = File(context.noBackupFilesDir, name)
val file = File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
if (!file.exists()) {
file.createNewFile()
}
return file
}
// override fun getOrCreateTempStorageFile(): File {
// val file = File(context.noBackupFilesDir, REMOTE_ADDRESS_BOOK_FILE_NAME_LOCAL_COPY)
// if (!file.exists()) {
// file.createNewFile()
// }
// return file
// }
private fun getOrCreateAddressBookDir(): File {
val filesDir = context.filesDir
val addressBookDir = File(filesDir, "address_book")
if (!addressBookDir.exists()) {
addressBookDir.mkdir()
}
return addressBookDir
}
}
private const val LEGACY_UNENCRYPTED_ADDRESS_BOOK_FILE_NAME = "address_book"
private const val REMOTE_ADDRESS_BOOK_FILE_NAME_LOCAL_COPY = "address_book_temp"
// private const val REMOTE_ADDRESS_BOOK_FILE_NAME_LOCAL_COPY = "address_book_temp"