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:
parent
95285c5133
commit
32b132950c
|
@ -8,6 +8,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Address book encryption
|
- Address book encryption
|
||||||
|
- Android auto backup support for address book encryption
|
||||||
- The device authentication feature on the Zashi app launch has been added
|
- 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.
|
- 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
|
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
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:icon="@mipmap/ic_launcher_square"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<full-backup-content>
|
||||||
|
<include
|
||||||
|
domain="file"
|
||||||
|
path="address_book/." />
|
||||||
|
</full-backup-content>
|
|
@ -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>
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||||
android:allowBackup="false"
|
|
||||||
android:icon="@mipmap/ic_launcher_square"
|
android:icon="@mipmap/ic_launcher_square"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||||
android:allowBackup="false"
|
|
||||||
android:icon="@mipmap/ic_launcher_square"
|
android:icon="@mipmap/ic_launcher_square"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||||
android:allowBackup="false"
|
|
||||||
android:icon="@mipmap/ic_launcher_square"
|
android:icon="@mipmap/ic_launcher_square"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
Here you'll find documentation
|
# Android auto backup testing
|
||||||
TBD
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
|
@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Address book encryption
|
- Address book encryption
|
||||||
|
- Android auto backup support for address book encryption
|
||||||
- The device authentication feature on the Zashi app launch has been added
|
- 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.
|
- 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
|
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
|
||||||
|
|
|
@ -11,6 +11,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Address book encryption
|
- Address book encryption
|
||||||
|
- Android auto backup support for address book encryption
|
||||||
- The device authentication feature on the Zashi app launch has been added
|
- 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.
|
- 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
|
- The Flexa SDK has been adopted to enable payments using the embedded Flexa UI
|
||||||
|
|
|
@ -11,8 +11,6 @@ import co.electriccoin.zcash.ui.common.serialization.addressbook.AddressBookKey
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import java.io.IOException
|
|
||||||
import java.security.GeneralSecurityException
|
|
||||||
|
|
||||||
interface LocalAddressBookDataSource {
|
interface LocalAddressBookDataSource {
|
||||||
suspend fun getContacts(addressBookKey: AddressBookKey): AddressBook
|
suspend fun getContacts(addressBookKey: AddressBookKey): AddressBook
|
||||||
|
@ -61,7 +59,9 @@ class LocalAddressBookDataSourceImpl(
|
||||||
lastUpdated = Clock.System.now(),
|
lastUpdated = Clock.System.now(),
|
||||||
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
version = ADDRESS_BOOK_SERIALIZATION_V1,
|
||||||
contacts = emptyList(),
|
contacts = emptyList(),
|
||||||
)
|
).also {
|
||||||
|
this@LocalAddressBookDataSourceImpl.addressBook = it
|
||||||
|
}
|
||||||
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
||||||
}
|
}
|
||||||
newAddressBook
|
newAddressBook
|
||||||
|
@ -88,7 +88,9 @@ class LocalAddressBookDataSourceImpl(
|
||||||
address = address,
|
address = address,
|
||||||
lastUpdated = lastUpdated,
|
lastUpdated = lastUpdated,
|
||||||
),
|
),
|
||||||
)
|
).also {
|
||||||
|
addressBook = it
|
||||||
|
}
|
||||||
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
||||||
newAddressBook
|
newAddressBook
|
||||||
}
|
}
|
||||||
|
@ -118,7 +120,9 @@ class LocalAddressBookDataSourceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
).also {
|
||||||
|
addressBook = it
|
||||||
|
}
|
||||||
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
||||||
newAddressBook
|
newAddressBook
|
||||||
}
|
}
|
||||||
|
@ -139,7 +143,9 @@ class LocalAddressBookDataSourceImpl(
|
||||||
remove(addressBookContact)
|
remove(addressBookContact)
|
||||||
}
|
}
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
).also {
|
||||||
|
addressBook = it
|
||||||
|
}
|
||||||
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
writeAddressBookToLocalStorage(newAddressBook, addressBookKey)
|
||||||
newAddressBook
|
newAddressBook
|
||||||
}
|
}
|
||||||
|
@ -158,22 +164,15 @@ class LocalAddressBookDataSourceImpl(
|
||||||
|
|
||||||
@Suppress("ReturnCount")
|
@Suppress("ReturnCount")
|
||||||
private suspend fun readLocalFileToAddressBook(addressBookKey: AddressBookKey): AddressBook? {
|
private suspend fun readLocalFileToAddressBook(addressBookKey: AddressBookKey): AddressBook? {
|
||||||
val encryptedFile = addressBookStorageProvider.getStorageFile(addressBookKey)
|
val encryptedFile = runCatching { addressBookStorageProvider.getStorageFile(addressBookKey) }.getOrNull()
|
||||||
val unencryptedFile = addressBookStorageProvider.getLegacyUnencryptedStorageFile()
|
val unencryptedFile = runCatching { addressBookStorageProvider.getLegacyUnencryptedStorageFile() }.getOrNull()
|
||||||
|
|
||||||
if (encryptedFile != null) {
|
if (encryptedFile != null) {
|
||||||
return try {
|
return runCatching {
|
||||||
addressBookProvider.readAddressBookFromFile(encryptedFile, addressBookKey)
|
addressBookProvider
|
||||||
.also {
|
.readAddressBookFromFile(encryptedFile, addressBookKey)
|
||||||
unencryptedFile?.deleteSuspend()
|
.also { unencryptedFile?.deleteSuspend() }
|
||||||
}
|
}.onFailure { e -> Twig.warn(e) { "Failed to decrypt address book" } }.getOrNull()
|
||||||
} 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 if (unencryptedFile != null) {
|
return if (unencryptedFile != null) {
|
||||||
|
@ -191,7 +190,9 @@ class LocalAddressBookDataSourceImpl(
|
||||||
addressBook: AddressBook,
|
addressBook: AddressBook,
|
||||||
addressBookKey: AddressBookKey
|
addressBookKey: AddressBookKey
|
||||||
) {
|
) {
|
||||||
val file = addressBookStorageProvider.getOrCreateStorageFile(addressBookKey)
|
runCatching {
|
||||||
addressBookProvider.writeAddressBookToFile(file, addressBook, addressBookKey)
|
val file = addressBookStorageProvider.getOrCreateStorageFile(addressBookKey)
|
||||||
|
addressBookProvider.writeAddressBookToFile(file, addressBook, addressBookKey)
|
||||||
|
}.onFailure { e -> Twig.warn(e) { "Failed to write address book" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,17 @@ interface AddressBookStorageProvider {
|
||||||
|
|
||||||
fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File
|
fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Create a temporary file into which data from remote is written. This file is removed after usage.
|
// * Create a temporary file into which data from remote is written. This file is removed after usage.
|
||||||
*/
|
// */
|
||||||
fun getOrCreateTempStorageFile(): File
|
// fun getOrCreateTempStorageFile(): File
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddressBookStorageProviderImpl(
|
class AddressBookStorageProviderImpl(
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : AddressBookStorageProvider {
|
) : AddressBookStorageProvider {
|
||||||
override fun getStorageFile(addressBookKey: AddressBookKey): File? {
|
override fun getStorageFile(addressBookKey: AddressBookKey): File? {
|
||||||
return File(context.noBackupFilesDir, addressBookKey.fileIdentifier())
|
return File(getOrCreateAddressBookDir(), addressBookKey.fileIdentifier())
|
||||||
.takeIf { it.exists() && it.isFile }
|
.takeIf { it.exists() && it.isFile }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,19 +31,30 @@ class AddressBookStorageProviderImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File {
|
override fun getOrCreateStorageFile(addressBookKey: AddressBookKey): File {
|
||||||
return getOrCreateFile(addressBookKey.fileIdentifier())
|
val file = File(getOrCreateAddressBookDir(), 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)
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
}
|
}
|
||||||
return file
|
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 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"
|
||||||
|
|
Loading…
Reference in New Issue