From 53937bda2f1bbe8f7659a17607c7c2311cdca71f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 24 Sep 2020 15:45:59 -0300 Subject: [PATCH] Auto-format code style of all files to match official style This also adds a note to the README and the Android Studio coding style files. --- .gitignore | 1 + .idea/codeStyles/Project.xml | 139 ++ .idea/codeStyles/codeStyleConfig.xml | 5 + README.md | 4 +- .../com/stevesoltys/seedvault/Base64Utils.kt | 2 +- .../seedvault/UsbIntentReceiver.kt | 6 +- .../stevesoltys/seedvault/crypto/Crypto.kt | 33 +- .../seedvault/crypto/KeyManager.kt | 8 +- .../stevesoltys/seedvault/header/Header.kt | 17 +- .../seedvault/header/HeaderReader.kt | 8 +- .../seedvault/header/HeaderWriter.kt | 4 +- .../seedvault/metadata/Metadata.kt | 49 +- .../seedvault/metadata/MetadataManager.kt | 11 +- .../seedvault/metadata/MetadataReader.kt | 62 +- .../restore/InstallProgressAdapter.kt | 20 +- .../seedvault/restore/RestorableBackup.kt | 6 +- .../restore/RestoreErrorBroadcastReceiver.kt | 1 + .../restore/RestoreProgressAdapter.kt | 15 +- .../restore/RestoreProgressFragment.kt | 7 +- .../seedvault/restore/RestoreSetAdapter.kt | 10 +- .../seedvault/restore/RestoreViewModel.kt | 138 +- .../seedvault/settings/AboutDialogFragment.kt | 7 +- .../seedvault/settings/AppStatusAdapter.kt | 28 +- .../seedvault/settings/AppStatusFragment.kt | 7 +- .../seedvault/settings/SettingsFragment.kt | 36 +- .../ConfigurableBackupTransportService.kt | 1 - .../seedvault/transport/backup/ApkBackup.kt | 2 +- .../transport/backup/BackupCoordinator.kt | 5 +- .../transport/backup/BackupModule.kt | 49 +- .../seedvault/transport/backup/FullBackup.kt | 14 +- .../seedvault/transport/backup/KVBackup.kt | 2 +- .../transport/backup/PackageService.kt | 6 +- .../transport/restore/ApkInstaller.kt | 17 +- .../seedvault/transport/restore/ApkRestore.kt | 61 +- .../transport/restore/FullRestore.kt | 16 +- .../seedvault/transport/restore/KVRestore.kt | 10 +- .../transport/restore/KVRestorePlugin.kt | 6 +- .../transport/restore/OutputFactory.kt | 2 +- .../seedvault/ui/BackupActivity.kt | 2 +- .../com/stevesoltys/seedvault/ui/LiveEvent.kt | 3 +- .../ui/RequireProvisioningViewModel.kt | 6 +- .../ui/recoverycode/RecoveryCodeActivity.kt | 6 +- .../ui/recoverycode/RecoveryCodeAdapter.kt | 5 +- .../recoverycode/RecoveryCodeInputFragment.kt | 3 +- .../ui/storage/RestoreStorageViewModel.kt | 10 +- .../seedvault/ui/storage/StorageActivity.kt | 8 +- .../ui/storage/StorageRootAdapter.kt | 27 +- .../ui/storage/StorageRootFetcher.kt | 98 +- .../ui/storage/StorageRootsFragment.kt | 2 +- app/src/main/res/drawable/nextcloud.xml | 7 +- .../res/drawable/nextcloud_background.xml | 1208 ++++++++--------- .../res/drawable/nextcloud_foreground.xml | 17 +- .../layout/fragment_recovery_code_input.xml | 2 +- .../res/layout/fragment_restore_progress.xml | 4 +- .../main/res/layout/fragment_restore_set.xml | 2 +- .../layout/list_item_recovery_code_output.xml | 2 +- .../main/res/mipmap-anydpi/ic_launcher.xml | 4 +- .../res/mipmap-anydpi/ic_launcher_round.xml | 4 +- .../com/stevesoltys/seedvault/TestUtils.kt | 12 +- .../seedvault/crypto/CryptoImplTest.kt | 4 +- .../seedvault/crypto/CryptoTest.kt | 49 +- .../seedvault/header/HeaderReaderTest.kt | 87 +- .../seedvault/metadata/MetadataManagerTest.kt | 96 +- .../seedvault/metadata/MetadataReaderTest.kt | 20 +- .../metadata/MetadataWriterDecoderTest.kt | 59 +- .../seedvault/plugins/saf/DocumentFileTest.kt | 4 +- .../transport/CoordinatorIntegrationTest.kt | 3 +- .../transport/backup/FullBackupTest.kt | 29 +- .../transport/restore/ApkRestoreTest.kt | 156 ++- .../transport/restore/FullRestoreTest.kt | 66 +- .../transport/restore/KVRestoreTest.kt | 76 +- .../restore/RestoreCoordinatorTest.kt | 46 +- 72 files changed, 1776 insertions(+), 1166 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.gitignore b/.gitignore index 9e827658..89216d50 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ out/ lib/ .idea/* !.idea/runConfigurations* +!.idea/codeStyles* *.ipr *.iws *.iml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..e122e4f9 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7b4b8759..7e1cb547 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ It uses the same internal APIs as `adb backup` which is deprecated and thus need * `android.permission.INSTALL_PACKAGES` to re-install apps when restoring from backup. ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/stevesoltys/seedvault. +Bug reports and pull requests are welcome on GitHub at https://github.com/stevesoltys/seedvault. + +This project aims to adhere to the [official Kotlin coding style](https://developer.android.com/kotlin/style-guide). ## License This application is available as open source under the terms of the [Apache-2.0 License](https://opensource.org/licenses/Apache-2.0). diff --git a/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt b/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt index b0ae1ef5..69571f51 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt @@ -1,7 +1,7 @@ package com.stevesoltys.seedvault import java.nio.charset.Charset -import java.util.* +import java.util.Base64 val Utf8: Charset = Charset.forName("UTF-8") diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt index db4d4c21..d5dd5084 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt @@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit.HOURS private val TAG = UsbIntentReceiver::class.java.simpleName +private const val HOURS_AUTO_BACKUP: Long = 24 + class UsbIntentReceiver : UsbMonitor() { // using KoinComponent would crash robolectric tests :( @@ -37,7 +39,8 @@ class UsbIntentReceiver : UsbMonitor() { val attachedFlashDrive = FlashDrive.from(device) return if (savedFlashDrive == attachedFlashDrive) { Log.d(TAG, "Matches stored device, checking backup time...") - if (System.currentTimeMillis() - metadataManager.getLastBackupTime() >= HOURS.toMillis(24)) { + val backupMillis = System.currentTimeMillis() - metadataManager.getLastBackupTime() + if (backupMillis >= HOURS.toMillis(HOURS_AUTO_BACKUP)) { Log.d(TAG, "Last backup older than 24 hours, requesting a backup...") true } else { @@ -101,6 +104,7 @@ internal fun UsbDevice.isMassStorage(): Boolean { } private fun UsbInterface.isMassStorage(): Boolean { + @Suppress("MagicNumber") return interfaceClass == 8 && interfaceProtocol == 80 && interfaceSubclass == 6 } diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt index 7d2415af..9165a8d7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt @@ -75,8 +75,12 @@ interface Crypto { * @return The read [VersionHeader] present in the beginning of the given [InputStream]. */ @Throws(IOException::class, SecurityException::class) - fun decryptHeader(inputStream: InputStream, expectedVersion: Byte, expectedPackageName: String, - expectedKey: String? = null): VersionHeader + fun decryptHeader( + inputStream: InputStream, + expectedVersion: Byte, + expectedPackageName: String, + expectedKey: String? = null + ): VersionHeader /** * Reads and decrypts a segment from the given [InputStream]. @@ -94,9 +98,10 @@ interface Crypto { } internal class CryptoImpl( - private val cipherFactory: CipherFactory, - private val headerWriter: HeaderWriter, - private val headerReader: HeaderReader) : Crypto { + private val cipherFactory: CipherFactory, + private val headerWriter: HeaderWriter, + private val headerReader: HeaderReader +) : Crypto { @Throws(IOException::class) override fun encryptHeader(outputStream: OutputStream, versionHeader: VersionHeader) { @@ -136,16 +141,26 @@ internal class CryptoImpl( } @Throws(IOException::class, SecurityException::class) - override fun decryptHeader(inputStream: InputStream, expectedVersion: Byte, - expectedPackageName: String, expectedKey: String?): VersionHeader { + override fun decryptHeader( + inputStream: InputStream, + expectedVersion: Byte, + expectedPackageName: String, + expectedKey: String? + ): VersionHeader { val decrypted = decryptSegment(inputStream, MAX_VERSION_HEADER_SIZE) val header = headerReader.getVersionHeader(decrypted) if (header.version != expectedVersion) { - throw SecurityException("Invalid version '${header.version.toInt()}' in header, expected '${expectedVersion.toInt()}'.") + throw SecurityException( + "Invalid version '${header.version.toInt()}' in header, " + + "expected '${expectedVersion.toInt()}'." + ) } if (header.packageName != expectedPackageName) { - throw SecurityException("Invalid package name '${header.packageName}' in header, expected '$expectedPackageName'.") + throw SecurityException( + "Invalid package name '${header.packageName}' in header, " + + "expected '$expectedPackageName'." + ) } if (header.key != expectedKey) { throw SecurityException("Invalid key '${header.key}' in header, expected '$expectedKey'.") diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt index 4ecb459e..e472bd39 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt @@ -54,7 +54,7 @@ internal class KeyManagerImpl : KeyManager { } override fun hasBackupKey() = keyStore.containsAlias(KEY_ALIAS) && - keyStore.entryInstanceOf(KEY_ALIAS, SecretKeyEntry::class.java) + keyStore.entryInstanceOf(KEY_ALIAS, SecretKeyEntry::class.java) override fun getBackupKey(): SecretKey { val ksEntry = keyStore.getEntry(KEY_ALIAS, null) as SecretKeyEntry @@ -63,9 +63,9 @@ internal class KeyManagerImpl : KeyManager { private fun getKeyProtection(): KeyProtection { val builder = KeyProtection.Builder(PURPOSE_ENCRYPT or PURPOSE_DECRYPT) - .setBlockModes(BLOCK_MODE_GCM) - .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) - .setRandomizedEncryptionRequired(true) + .setBlockModes(BLOCK_MODE_GCM) + .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) + .setRandomizedEncryptionRequired(true) // unlocking is required only for decryption, so when restoring from backup builder.setUnlockedDeviceRequired(true) return builder.build() diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt b/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt index 28a8ee41..6017d4f9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt @@ -5,16 +5,17 @@ import com.stevesoltys.seedvault.crypto.GCM_AUTHENTICATION_TAG_LENGTH internal const val VERSION: Byte = 0 internal const val MAX_PACKAGE_LENGTH_SIZE = 255 internal const val MAX_KEY_LENGTH_SIZE = MAX_PACKAGE_LENGTH_SIZE -internal const val MAX_VERSION_HEADER_SIZE = 1 + Short.SIZE_BYTES * 2 + MAX_PACKAGE_LENGTH_SIZE + MAX_KEY_LENGTH_SIZE +internal const val MAX_VERSION_HEADER_SIZE = + 1 + Short.SIZE_BYTES * 2 + MAX_PACKAGE_LENGTH_SIZE + MAX_KEY_LENGTH_SIZE /** * After the first version byte of each backup stream * must follow followed this header encrypted with authentication. */ data class VersionHeader( - internal val version: Byte = VERSION, // 1 byte - internal val packageName: String, // ?? bytes (max 255) - internal val key: String? = null // ?? bytes + internal val version: Byte = VERSION, // 1 byte + internal val packageName: String, // ?? bytes (max 255) + internal val key: String? = null // ?? bytes ) { init { check(packageName.length <= MAX_PACKAGE_LENGTH_SIZE) { @@ -26,10 +27,10 @@ data class VersionHeader( } } - internal const val SEGMENT_LENGTH_SIZE: Int = Short.SIZE_BYTES internal const val MAX_SEGMENT_LENGTH: Int = Short.MAX_VALUE.toInt() -internal const val MAX_SEGMENT_CLEARTEXT_LENGTH: Int = MAX_SEGMENT_LENGTH - GCM_AUTHENTICATION_TAG_LENGTH / 8 +internal const val MAX_SEGMENT_CLEARTEXT_LENGTH: Int = + MAX_SEGMENT_LENGTH - GCM_AUTHENTICATION_TAG_LENGTH / 8 internal const val IV_SIZE: Int = 12 internal const val SEGMENT_HEADER_SIZE = SEGMENT_LENGTH_SIZE + IV_SIZE @@ -37,8 +38,8 @@ internal const val SEGMENT_HEADER_SIZE = SEGMENT_LENGTH_SIZE + IV_SIZE * Each data segment must start with this header */ class SegmentHeader( - internal val segmentLength: Short, // 2 bytes - internal val nonce: ByteArray // 12 bytes + internal val segmentLength: Short, // 2 bytes + internal val nonce: ByteArray // 12 bytes ) { init { check(nonce.size == IV_SIZE) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt index f9ef3900..419f551d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt @@ -36,16 +36,16 @@ internal class HeaderReaderImpl : HeaderReader { if (packageLength > MAX_PACKAGE_LENGTH_SIZE) throw SecurityException("Too large package length: $packageLength") if (packageLength > buffer.remaining()) throw SecurityException("Not enough bytes for package name") val packageName = ByteArray(packageLength) - .apply { buffer.get(this) } - .toString(Utf8) + .apply { buffer.get(this) } + .toString(Utf8) val keyLength = buffer.short.toInt() if (keyLength < 0) throw SecurityException("Invalid key length: $keyLength") if (keyLength > MAX_KEY_LENGTH_SIZE) throw SecurityException("Too large key length: $keyLength") if (keyLength > buffer.remaining()) throw SecurityException("Not enough bytes for key") val key = if (keyLength == 0) null else ByteArray(keyLength) - .apply { buffer.get(this) } - .toString(Utf8) + .apply { buffer.get(this) } + .toString(Utf8) if (buffer.remaining() != 0) throw SecurityException("Found extra bytes in header") diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderWriter.kt index 65066374..2bd27aec 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderWriter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderWriter.kt @@ -43,8 +43,8 @@ internal class HeaderWriterImpl : HeaderWriter { override fun writeSegmentHeader(outputStream: OutputStream, header: SegmentHeader) { val buffer = ByteBuffer.allocate(SEGMENT_HEADER_SIZE) - .putShort(header.segmentLength) - .put(header.nonce) + .putShort(header.segmentLength) + .put(header.nonce) outputStream.write(buffer.array()) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt index 354f8de5..50385c7f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt @@ -9,13 +9,13 @@ import java.io.InputStream typealias PackageMetadataMap = HashMap data class BackupMetadata( - internal val version: Byte = VERSION, - internal val token: Long, - internal var time: Long = 0L, - internal val androidVersion: Int = Build.VERSION.SDK_INT, - internal val androidIncremental: String = Build.VERSION.INCREMENTAL, - internal val deviceName: String = "${Build.MANUFACTURER} ${Build.MODEL}", - internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap() + internal val version: Byte = VERSION, + internal val token: Long, + internal var time: Long = 0L, + internal val androidVersion: Int = Build.VERSION.SDK_INT, + internal val androidIncremental: String = Build.VERSION.INCREMENTAL, + internal val deviceName: String = "${Build.MANUFACTURER} ${Build.MODEL}", + internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap() ) internal const val JSON_METADATA = "@meta@" @@ -32,23 +32,28 @@ enum class PackageState { * This is the expected state of all user-installed packages. */ APK_AND_DATA, + /** * Package data could not get backed up, because the app exceeded the allowed quota. */ QUOTA_EXCEEDED, + /** * Package data could not get backed up, because the app reported no data to back up. */ NO_DATA, + /** * Package data could not get backed up, because the app has [FLAG_STOPPED]. */ WAS_STOPPED, + /** * Package data could not get backed up, because it was not allowed. * Most often, this is a manifest opt-out, but it could also be a disabled or system-user app. */ NOT_ALLOWED, + /** * Package data could not get backed up, because an error occurred during backup. */ @@ -56,17 +61,17 @@ enum class PackageState { } data class PackageMetadata( - /** - * The timestamp in milliseconds of the last app data backup. - * It is 0 if there never was a data backup. - */ - internal var time: Long = 0L, - internal var state: PackageState = UNKNOWN_ERROR, - internal val system: Boolean = false, - internal val version: Long? = null, - internal val installer: String? = null, - internal val sha256: String? = null, - internal val signatures: List? = null + /** + * The timestamp in milliseconds of the last app data backup. + * It is 0 if there never was a data backup. + */ + internal var time: Long = 0L, + internal var state: PackageState = UNKNOWN_ERROR, + internal val system: Boolean = false, + internal val version: Long? = null, + internal val installer: String? = null, + internal val sha256: String? = null, + internal val signatures: List? = null ) { fun hasApk(): Boolean { return version != null && sha256 != null && signatures != null @@ -84,11 +89,13 @@ internal const val JSON_PACKAGE_SIGNATURES = "signatures" internal class DecryptionFailedException(cause: Throwable) : Exception(cause) class EncryptedBackupMetadata private constructor( - val token: Long, - val inputStream: InputStream?, - val error: Boolean) { + val token: Long, + val inputStream: InputStream?, + val error: Boolean +) { constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false) + /** * Indicates that there was an error retrieving the encrypted backup metadata. */ diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt index 61efe34e..dc10762f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt @@ -182,7 +182,10 @@ class MetadataManager( * 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()")) + @Deprecated( + "Responsibility for current token moved to SettingsManager", + ReplaceWith("settingsManager.getToken()") + ) fun getBackupToken(): Long = metadata.token /** @@ -207,9 +210,9 @@ class MetadataManager( // because we have no way to also include upgraded system apps return metadata.packageMetadataMap.filter { (_, packageMetadata) -> !packageMetadata.system && ( // ignore system apps - packageMetadata.state == APK_AND_DATA || // either full success - packageMetadata.state == NO_DATA // or apps that simply had no data - ) + packageMetadata.state == APK_AND_DATA || // either full success + packageMetadata.state == NO_DATA // or apps that simply had no data + ) }.count() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt index c1686ca8..7c840c30 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt @@ -18,17 +18,31 @@ import javax.crypto.AEADBadTagException interface MetadataReader { - @Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class) + @Throws( + SecurityException::class, + DecryptionFailedException::class, + UnsupportedVersionException::class, + IOException::class + ) fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata @Throws(SecurityException::class) - fun decode(bytes: ByteArray, expectedVersion: Byte? = null, expectedToken: Long? = null): BackupMetadata + fun decode( + bytes: ByteArray, + expectedVersion: Byte? = null, + expectedToken: Long? = null + ): BackupMetadata } internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { - @Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class) + @Throws( + SecurityException::class, + DecryptionFailedException::class, + UnsupportedVersionException::class, + IOException::class + ) override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata { val version = inputStream.read().toByte() if (version < 0) throw IOException() @@ -42,7 +56,11 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { } @Throws(SecurityException::class) - override fun decode(bytes: ByteArray, expectedVersion: Byte?, expectedToken: Long?): BackupMetadata { + override fun decode( + bytes: ByteArray, + expectedVersion: Byte?, + expectedToken: Long? + ): BackupMetadata { // NOTE: We don't do extensive validation of the parsed input here, // because it was encrypted with authentication, so we should be able to trust it. // @@ -54,7 +72,10 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { val meta = json.getJSONObject(JSON_METADATA) val version = meta.getInt(JSON_METADATA_VERSION).toByte() if (expectedVersion != null && version != expectedVersion) { - throw SecurityException("Invalid version '${version.toInt()}' in metadata, expected '${expectedVersion.toInt()}'.") + throw SecurityException( + "Invalid version '${version.toInt()}' in metadata," + + "expected '${expectedVersion.toInt()}'." + ) } val token = meta.getLong(JSON_METADATA_TOKEN) if (expectedToken != null && token != expectedToken) { @@ -78,30 +99,31 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { val pInstaller = p.optString(JSON_PACKAGE_INSTALLER) val pSha256 = p.optString(JSON_PACKAGE_SHA256) val pSignatures = p.optJSONArray(JSON_PACKAGE_SIGNATURES) - val signatures = if (pSignatures == null) null else + val signatures = if (pSignatures == null) null else { ArrayList(pSignatures.length()).apply { for (i in (0 until pSignatures.length())) { add(pSignatures.getString(i)) } } + } packageMetadataMap[packageName] = PackageMetadata( - time = p.getLong(JSON_PACKAGE_TIME), - state = pState, - system = pSystem, - version = if (pVersion == 0L) null else pVersion, - installer = if (pInstaller == "") null else pInstaller, - sha256 = if (pSha256 == "") null else pSha256, - signatures = signatures + time = p.getLong(JSON_PACKAGE_TIME), + state = pState, + system = pSystem, + version = if (pVersion == 0L) null else pVersion, + installer = if (pInstaller == "") null else pInstaller, + sha256 = if (pSha256 == "") null else pSha256, + signatures = signatures ) } return BackupMetadata( - version = version, - token = token, - time = meta.getLong(JSON_METADATA_TIME), - androidVersion = meta.getInt(JSON_METADATA_SDK_INT), - androidIncremental = meta.getString(JSON_METADATA_INCREMENTAL), - deviceName = meta.getString(JSON_METADATA_NAME), - packageMetadataMap = packageMetadataMap + version = version, + token = token, + time = meta.getLong(JSON_METADATA_TIME), + androidVersion = meta.getInt(JSON_METADATA_SDK_INT), + androidIncremental = meta.getString(JSON_METADATA_INCREMENTAL), + deviceName = meta.getString(JSON_METADATA_NAME), + packageMetadataMap = packageMetadataMap ) } catch (e: JSONException) { throw SecurityException(e) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt index 34ef126b..ed11737a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt @@ -18,14 +18,22 @@ import com.stevesoltys.seedvault.ui.AppViewHolder internal class InstallProgressAdapter : Adapter() { - private val items = SortedList(ApkRestoreResult::class.java, object : SortedListAdapterCallback(this) { - override fun areItemsTheSame(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.packageName == item2.packageName - override fun areContentsTheSame(oldItem: ApkRestoreResult, newItem: ApkRestoreResult) = oldItem == newItem - override fun compare(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.compareTo(item2) - }) + private val items = SortedList( + ApkRestoreResult::class.java, + object : SortedListAdapterCallback(this) { + override fun areItemsTheSame(item1: ApkRestoreResult, item2: ApkRestoreResult) = + item1.packageName == item2.packageName + + override fun areContentsTheSame(oldItem: ApkRestoreResult, newItem: ApkRestoreResult) = + oldItem == newItem + + override fun compare(item1: ApkRestoreResult, item2: ApkRestoreResult) = + item1.compareTo(item2) + }) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppInstallViewHolder { - val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false) + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_app_status, parent, false) return AppInstallViewHolder(v) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt index 34cab843..953dd750 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt @@ -4,8 +4,10 @@ import android.app.backup.RestoreSet import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -data class RestorableBackup(private val restoreSet: RestoreSet, - private val backupMetadata: BackupMetadata) { +data class RestorableBackup( + private val restoreSet: RestoreSet, + private val backupMetadata: BackupMetadata +) { val name: String get() = restoreSet.name diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt index 1b5343fb..40d88041 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt @@ -23,6 +23,7 @@ class RestoreErrorBroadcastReceiver : BroadcastReceiver() { notificationManager.onRestoreErrorSeen() val packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME)!! + @Suppress("DEPRECATION") // the alternative doesn't work for us val i = Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply { data = "package:$packageName".toUri() diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt index 588539ba..ea5740ce 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt @@ -17,7 +17,8 @@ internal class RestoreProgressAdapter : Adapter() { private val items = LinkedList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder { - val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false) + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_app_status, parent, false) return PackageViewHolder(v) } @@ -35,8 +36,9 @@ internal class RestoreProgressAdapter : Adapter() { } private class Diff( - private val oldItems: LinkedList, - private val newItems: LinkedList) : DiffUtil.Callback() { + private val oldItems: LinkedList, + private val newItems: LinkedList + ) : DiffUtil.Callback() { override fun getOldListSize() = oldItems.size override fun getNewListSize() = newItems.size @@ -81,6 +83,7 @@ enum class AppRestoreStatus { } internal data class AppRestoreResult( - val packageName: String, - val name: CharSequence, - val status: AppRestoreStatus) + val packageName: String, + val name: CharSequence, + val status: AppRestoreStatus +) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt index 0418945a..adec8467 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt @@ -32,8 +32,11 @@ class RestoreProgressFragment : Fragment() { private lateinit var appList: RecyclerView private lateinit var button: Button - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { val v: View = inflater.inflate(R.layout.fragment_restore_progress, container, false) progressBar = v.findViewById(R.id.progressBar) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt index 8acb0eef..34e00209 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt @@ -13,12 +13,13 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder internal class RestoreSetAdapter( - private val listener: RestorableBackupClickListener, - private val items: List) : Adapter() { + private val listener: RestorableBackupClickListener, + private val items: List +) : Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RestoreSetViewHolder { val v = LayoutInflater.from(parent.context) - .inflate(R.layout.list_item_restore_set, parent, false) as View + .inflate(R.layout.list_item_restore_set, parent, false) as View return RestoreSetViewHolder(v) } @@ -39,7 +40,8 @@ internal class RestoreSetAdapter( val lastBackup = getRelativeTime(item.time) val setup = getRelativeTime(item.token) - subtitleView.text = v.context.getString(R.string.restore_restore_set_times, lastBackup, setup) + subtitleView.text = + v.context.getString(R.string.restore_restore_set_times, lastBackup, setup) } private fun getRelativeTime(time: Long): CharSequence { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 68a9cfcd..2114ec37 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -19,7 +19,7 @@ import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.ui.notification.getAppName +import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA @@ -44,6 +44,7 @@ import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel +import com.stevesoltys.seedvault.ui.notification.getAppName import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -52,7 +53,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import java.util.* +import java.util.LinkedList import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -60,13 +61,13 @@ import kotlin.coroutines.suspendCoroutine private val TAG = RestoreViewModel::class.java.simpleName internal class RestoreViewModel( - app: Application, - settingsManager: SettingsManager, - keyManager: KeyManager, - private val backupManager: IBackupManager, - private val restoreCoordinator: RestoreCoordinator, - private val apkRestore: ApkRestore, - private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO + app: Application, + settingsManager: SettingsManager, + keyManager: KeyManager, + private val backupManager: IBackupManager, + private val restoreCoordinator: RestoreCoordinator, + private val apkRestore: ApkRestore, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : RequireProvisioningViewModel(app, settingsManager, keyManager), RestorableBackupClickListener { override val isRestoreOperation = true @@ -83,17 +84,24 @@ internal class RestoreViewModel( private val mChosenRestorableBackup = MutableLiveData() internal val chosenRestorableBackup: LiveData get() = mChosenRestorableBackup - internal val installResult: LiveData = switchMap(mChosenRestorableBackup) { backup -> - @Suppress("EXPERIMENTAL_API_USAGE") - getInstallResult(backup) - } + internal val installResult: LiveData = + switchMap(mChosenRestorableBackup) { backup -> + @Suppress("EXPERIMENTAL_API_USAGE") + getInstallResult(backup) + } private val mNextButtonEnabled = MutableLiveData().apply { value = false } internal val nextButtonEnabled: LiveData = mNextButtonEnabled private val mRestoreProgress = MutableLiveData>().apply { value = LinkedList().apply { - add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, getAppName(app, MAGIC_PACKAGE_MANAGER), IN_PROGRESS)) + add( + AppRestoreResult( + packageName = MAGIC_PACKAGE_MANAGER, + name = getAppName(app, MAGIC_PACKAGE_MANAGER), + status = IN_PROGRESS + ) + ) } } internal val restoreProgress: LiveData> get() = mRestoreProgress @@ -104,8 +112,8 @@ internal class RestoreViewModel( @Throws(RemoteException::class) private fun getOrStartSession(): IRestoreSession { val session = this.session - ?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID) - ?: throw RemoteException("beginRestoreSessionForUser returned null") + ?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID) + ?: throw RemoteException("beginRestoreSessionForUser returned null") this.session = session return session } @@ -114,23 +122,24 @@ internal class RestoreViewModel( mRestoreSetResults.value = getAvailableRestoreSets() } - private suspend fun getAvailableRestoreSets() = suspendCoroutine { continuation -> - val session = try { - getOrStartSession() - } catch (e: RemoteException) { - Log.e(TAG, "Error starting new session", e) - continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error))) - return@suspendCoroutine - } + private suspend fun getAvailableRestoreSets() = + suspendCoroutine { continuation -> + val session = try { + getOrStartSession() + } catch (e: RemoteException) { + Log.e(TAG, "Error starting new session", e) + continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error))) + return@suspendCoroutine + } - val observer = RestoreObserver(continuation) - val setResult = session.getAvailableRestoreSets(observer, monitor) - if (setResult != 0) { - Log.e(TAG, "getAvailableRestoreSets() returned non-zero value") - continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error))) - return@suspendCoroutine + val observer = RestoreObserver(continuation) + val setResult = session.getAvailableRestoreSets(observer, monitor) + if (setResult != 0) { + Log.e(TAG, "getAvailableRestoreSets() returned non-zero value") + continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error))) + return@suspendCoroutine + } } - } override fun onRestorableBackupClicked(restorableBackup: RestorableBackup) { mChosenRestorableBackup.value = restorableBackup @@ -144,16 +153,16 @@ internal class RestoreViewModel( @ExperimentalCoroutinesApi private fun getInstallResult(restorableBackup: RestorableBackup): LiveData { return apkRestore.restore(restorableBackup.token, restorableBackup.packageMetadataMap) - .onStart { - Log.d(TAG, "Start InstallResult Flow") - }.catch { e -> - Log.d(TAG, "Exception in InstallResult Flow", e) - }.onCompletion { e -> - Log.d(TAG, "Completed InstallResult Flow", e) - mNextButtonEnabled.postValue(true) - } - .flowOn(ioDispatcher) - .asLiveData() + .onStart { + Log.d(TAG, "Start InstallResult Flow") + }.catch { e -> + Log.d(TAG, "Exception in InstallResult Flow", e) + }.onCompletion { e -> + Log.d(TAG, "Completed InstallResult Flow", e) + mNextButtonEnabled.postValue(true) + } + .flowOn(ioDispatcher) + .asLiveData() } internal fun onNextClicked() { @@ -216,7 +225,10 @@ internal class RestoreViewModel( } @WorkerThread - private fun getFailedStatus(packageName: String, restorableBackup: RestorableBackup = chosenRestorableBackup.value!!): AppRestoreStatus { + private fun getFailedStatus( + packageName: String, + restorableBackup: RestorableBackup = chosenRestorableBackup.value!! + ): AppRestoreStatus { val metadata = restorableBackup.packageMetadataMap[packageName] ?: return FAILED return when (metadata.state) { NO_DATA -> FAILED_NO_DATA @@ -267,7 +279,9 @@ internal class RestoreViewModel( } @WorkerThread - private inner class RestoreObserver(private val continuation: Continuation? = null) : IRestoreObserver.Stub() { + private inner class RestoreObserver( + private val continuation: Continuation? = null + ) : IRestoreObserver.Stub() { /** * Supply a list of the restore datasets available from the current transport. @@ -290,20 +304,7 @@ internal class RestoreViewModel( RestoreSetResult(app.getString(R.string.restore_set_error)) } else { val restorableBackups = restoreSets.mapNotNull { set -> - val metadata = backupMetadata[set.token] - when { - metadata == null -> { - Log.e(TAG, "RestoreCoordinator#getAndClearBackupMetadata() has no metadata for token ${set.token}.") - null - } - metadata.time == 0L -> { - Log.d(TAG, "Ignoring RestoreSet with no last backup time: ${set.token}.") - null - } - else -> { - RestorableBackup(set, metadata) - } - } + getRestorableBackup(set, backupMetadata[set.token]) } if (restorableBackups.isEmpty()) RestoreSetResult(app.getString(R.string.restore_set_empty_result)) else RestoreSetResult(restorableBackups) @@ -312,6 +313,20 @@ internal class RestoreViewModel( continuation.resume(result) } + private fun getRestorableBackup(set: RestoreSet, metadata: BackupMetadata?) = when { + metadata == null -> { + Log.e(TAG, "No metadata for token ${set.token}.") + null + } + metadata.time == 0L -> { + Log.d(TAG, "Ignoring RestoreSet with no last backup time: ${set.token}.") + null + } + else -> { + RestorableBackup(set, metadata) + } + } + /** * The restore operation has begun. * @@ -343,8 +358,8 @@ internal class RestoreViewModel( */ override fun restoreFinished(result: Int) { val restoreResult = RestoreBackupResult( - if (result == 0) null - else app.getString(R.string.restore_finished_error) + if (result == 0) null + else app.getString(R.string.restore_finished_error) ) onRestoreComplete(restoreResult) closeSession() @@ -355,8 +370,9 @@ internal class RestoreViewModel( } internal class RestoreSetResult( - internal val restorableBackups: List, - internal val errorMsg: String?) { + internal val restorableBackups: List, + internal val errorMsg: String? +) { internal constructor(restorableBackups: List) : this(restorableBackups, null) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt index e06a598f..1c842d95 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt @@ -20,8 +20,11 @@ class AboutDialogFragment : DialogFragment() { internal val TAG = AboutDialogFragment::class.java.simpleName } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { val v: View = inflater.inflate(R.layout.fragment_about, container, false) licenseView = v.findViewById(R.id.licenseView) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt index 515e6160..3fba9c44 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt @@ -22,13 +22,15 @@ import com.stevesoltys.seedvault.settings.AppStatusAdapter.AppStatusViewHolder import com.stevesoltys.seedvault.ui.AppViewHolder import com.stevesoltys.seedvault.ui.toRelativeTime -internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : Adapter() { +internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : + Adapter() { private val items = ArrayList() private var editMode = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppStatusViewHolder { - val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false) + val v = LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_app_status, parent, false) return AppStatusViewHolder(v) } @@ -103,16 +105,18 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe } data class AppStatus( - val packageName: String, - var enabled: Boolean, - val icon: Drawable, - val name: String, - val time: Long, - val status: AppRestoreStatus) + val packageName: String, + var enabled: Boolean, + val icon: Drawable, + val name: String, + val time: Long, + val status: AppRestoreStatus +) internal class AppStatusDiff( - private val oldItems: List, - private val newItems: List) : DiffUtil.Callback() { + private val oldItems: List, + private val newItems: List +) : DiffUtil.Callback() { override fun getOldListSize() = oldItems.size override fun getNewListSize() = newItems.size @@ -127,6 +131,6 @@ internal class AppStatusDiff( } internal class AppStatusResult( - val appStatusList: List, - val diff: DiffResult + val appStatusList: List, + val diff: DiffResult ) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt index d1ceef5a..2f8538cb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt @@ -32,8 +32,11 @@ class AppStatusFragment : Fragment(), AppStatusToggleListener { private lateinit var list: RecyclerView private lateinit var progressBar: ProgressBar - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { setHasOptionsMenu(true) val v: View = inflater.inflate(R.layout.fragment_app_status, container, false) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt index 276533a7..393d08a8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -53,7 +53,11 @@ class SettingsFragment : PreferenceFragmentCompat() { addAction(ACTION_USB_DEVICE_DETACHED) } private val usbReceiver = object : UsbMonitor() { - override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean { + override fun shouldMonitorStatus( + context: Context, + action: String, + device: UsbDevice + ): Boolean { return device.isMassStorage() } @@ -104,17 +108,17 @@ class SettingsFragment : PreferenceFragmentCompat() { val enable = newValue as Boolean if (enable) return@OnPreferenceChangeListener true AlertDialog.Builder(requireContext()) - .setIcon(R.drawable.ic_warning) - .setTitle(R.string.settings_backup_apk_dialog_title) - .setMessage(R.string.settings_backup_apk_dialog_message) - .setPositiveButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ -> - dialog.dismiss() - } - .setNegativeButton(R.string.settings_backup_apk_dialog_disable) { dialog, _ -> - apkBackup.isChecked = enable - dialog.dismiss() - } - .show() + .setIcon(R.drawable.ic_warning) + .setTitle(R.string.settings_backup_apk_dialog_title) + .setMessage(R.string.settings_backup_apk_dialog_message) + .setPositiveButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ -> + dialog.dismiss() + } + .setNegativeButton(R.string.settings_backup_apk_dialog_disable) { dialog, _ -> + apkBackup.isChecked = enable + dialog.dismiss() + } + .show() return@OnPreferenceChangeListener false } backupStatus = findPreference("backup_status")!! @@ -123,7 +127,9 @@ class SettingsFragment : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.lastBackupTime.observe(viewLifecycleOwner, Observer { time -> setAppBackupStatusSummary(time) }) + viewModel.lastBackupTime.observe(viewLifecycleOwner, Observer { time -> + setAppBackupStatusSummary(time) + }) } override fun onStart() { @@ -192,7 +198,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val storage = this.storage if (storage?.isUsb == true) { autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" + - getString(R.string.settings_auto_restore_summary_usb, storage.name) + getString(R.string.settings_auto_restore_summary_usb, storage.name) } else { autoRestore.setSummary(R.string.settings_auto_restore_summary) } @@ -214,7 +220,7 @@ class SettingsFragment : PreferenceFragmentCompat() { if (menuBackupNow != null && menuRestore != null) { val storage = this.storage val enabled = storage != null && - (!storage.isUsb || storage.getDocumentFile(context).isDirectory) + (!storage.isUsb || storage.getDocumentFile(context).isDirectory) menuBackupNow?.isEnabled = enabled menuRestore?.isEnabled = enabled } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt index 9381179b..9ac50ac9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt @@ -3,7 +3,6 @@ package com.stevesoltys.seedvault.transport import android.app.Service import android.app.backup.BackupManager import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP -import android.app.backup.BackupTransport.FLAG_USER_INITIATED import android.app.backup.IBackupManager import android.content.Context import android.content.Context.BACKUP_SERVICE diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt index 4cf7ce3e..e54e3209 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt @@ -81,7 +81,7 @@ class ApkBackup( Log.d( TAG, "Package $packageName with version $version already has a backup ($backedUpVersion)" + - " with the same signature. Not backing it up." + " with the same signature. Not backing it up." ) return null } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 37eee54e..f509fc90 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -358,7 +358,10 @@ internal class BackupCoordinator( val packageMetadata = metadataManager.getPackageMetadata(packageName) val oldPackageState = packageMetadata?.state if (oldPackageState != null && oldPackageState != packageState) { - Log.e(TAG, "Package $packageName was in $oldPackageState, update to $packageState") + Log.e( + TAG, + "Package $packageName was in $oldPackageState, update to $packageState" + ) plugin.getMetadataOutputStream().use { metadataManager.onPackageBackupError(packageInfo, packageState, it) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt index 700379e9..f2a3378c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt @@ -5,9 +5,48 @@ import org.koin.dsl.module val backupModule = module { single { InputFactory() } - single { PackageService(androidContext(), get()) } - single { ApkBackup(androidContext().packageManager, get(), get()) } - single { KVBackup(get().kvBackupPlugin, get(), get(), get(), get()) } - single { FullBackup(get().fullBackupPlugin, get(), get(), get()) } - single { BackupCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } + single { + PackageService( + context = androidContext(), + backupManager = get() + ) + } + single { + ApkBackup( + pm = androidContext().packageManager, + settingsManager = get(), + metadataManager = get() + ) + } + single { + KVBackup( + plugin = get().kvBackupPlugin, + inputFactory = get(), + headerWriter = get(), + crypto = get(), + nm = get() + ) + } + single { + FullBackup( + plugin = get().fullBackupPlugin, + inputFactory = get(), + headerWriter = get(), + crypto = get() + ) + } + single { + BackupCoordinator( + context = androidContext(), + plugin = get(), + kv = get(), + full = get(), + apkBackup = get(), + clock = get(), + packageService = get(), + metadataManager = get(), + settingsManager = get(), + nm = get() + ) + } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt index e2819f21..85e2591c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt @@ -18,14 +18,14 @@ import java.io.InputStream import java.io.OutputStream private class FullBackupState( - internal val packageInfo: PackageInfo, - internal val inputFileDescriptor: ParcelFileDescriptor, - internal val inputStream: InputStream, - internal var outputStreamInit: (suspend () -> OutputStream)? + val packageInfo: PackageInfo, + val inputFileDescriptor: ParcelFileDescriptor, + val inputStream: InputStream, + var outputStreamInit: (suspend () -> OutputStream)? ) { - internal var outputStream: OutputStream? = null - internal val packageName: String = packageInfo.packageName - internal var size: Long = 0 + var outputStream: OutputStream? = null + val packageName: String = packageInfo.packageName + var size: Long = 0 } const val DEFAULT_QUOTA_FULL_BACKUP = (2 * (25 * 1024 * 1024)).toLong() diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt index 458af402..be0734c9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt @@ -83,7 +83,7 @@ internal class KVBackup( if (isIncremental && !hasDataForPackage) { Log.w( TAG, "Requested incremental, but transport currently stores no data" + - " for $packageName, requesting non-incremental retry." + " for $packageName, requesting non-incremental retry." ) return backupError(TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt index 489083cf..a547bf5f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt @@ -73,8 +73,8 @@ internal class PackageService( return packageManager.getInstalledPackages(GET_SIGNING_CERTIFICATES) .filter { packageInfo -> packageInfo.doesNotGetBackedUp() && // only apps that do not allow backup - !packageInfo.isNotUpdatedSystemApp() && // and are not vanilla system apps - packageInfo.packageName != context.packageName // not this app + !packageInfo.isNotUpdatedSystemApp() && // and are not vanilla system apps + packageInfo.packageName != context.packageName // not this app }.sortedBy { packageInfo -> packageInfo.packageName }.also { notAllowed -> @@ -155,7 +155,7 @@ internal fun PackageInfo.isNotUpdatedSystemApp(): Boolean { internal fun PackageInfo.doesNotGetBackedUp(): Boolean { if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true return applicationInfo.flags and FLAG_ALLOW_BACKUP == 0 || // does not allow backup - applicationInfo.flags and FLAG_STOPPED != 0 // is stopped + applicationInfo.flags and FLAG_STOPPED != 0 // is stopped } internal fun PackageInfo.isStopped(): Boolean { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt index cd8662c2..c0844505 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt @@ -36,7 +36,12 @@ internal class ApkInstaller(private val context: Context) { @ExperimentalCoroutinesApi @Throws(IOException::class, SecurityException::class) - internal fun install(cachedApk: File, packageName: String, installerPackageName: String?, installResult: MutableInstallResult) = callbackFlow { + internal fun install( + cachedApk: File, + packageName: String, + installerPackageName: String?, + installResult: MutableInstallResult + ) = callbackFlow { val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, i: Intent) { if (i.action != BROADCAST_ACTION) return @@ -76,11 +81,17 @@ internal class ApkInstaller(private val context: Context) { flags = FLAG_RECEIVER_FOREGROUND setPackage(context.packageName) } - val pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent, FLAG_UPDATE_CURRENT) + val pendingIntent = + PendingIntent.getBroadcast(context, 0, broadcastIntent, FLAG_UPDATE_CURRENT) return pendingIntent.intentSender } - private fun onBroadcastReceived(i: Intent, expectedPackageName: String, cachedApk: File, installResult: MutableInstallResult): InstallResult { + private fun onBroadcastReceived( + i: Intent, + expectedPackageName: String, + cachedApk: File, + installResult: MutableInstallResult + ): InstallResult { val packageName = i.getStringExtra(EXTRA_PACKAGE_NAME)!! val success = i.getIntExtra(EXTRA_STATUS, -1) == STATUS_SUCCESS val statusMsg = i.getStringExtra(EXTRA_STATUS_MESSAGE)!! diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt index 3a358bf4..545e6d53 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt @@ -23,13 +23,13 @@ import java.io.IOException import java.security.MessageDigest import java.util.concurrent.ConcurrentHashMap - private val TAG = ApkRestore::class.java.simpleName internal class ApkRestore( - private val context: Context, - private val restorePlugin: RestorePlugin, - private val apkInstaller: ApkInstaller = ApkInstaller(context)) { + private val context: Context, + private val restorePlugin: RestorePlugin, + private val apkInstaller: ApkInstaller = ApkInstaller(context) +) { private val pm = context.packageManager @@ -51,7 +51,7 @@ internal class ApkRestore( // restore individual packages and emit updates for ((packageName, metadata) in packages) { try { - @Suppress("BlockingMethodInNonBlockingContext") // flows on Dispatcher.IO + @Suppress("BlockingMethodInNonBlockingContext") // flows on Dispatcher.IO restore(token, packageName, metadata, installResult).collect { emit(it) } @@ -69,9 +69,14 @@ internal class ApkRestore( } @ExperimentalCoroutinesApi - @Suppress("BlockingMethodInNonBlockingContext") // flows on Dispatcher.IO + @Suppress("BlockingMethodInNonBlockingContext") // flows on Dispatcher.IO @Throws(IOException::class, SecurityException::class) - private fun restore(token: Long, packageName: String, metadata: PackageMetadata, installResult: MutableInstallResult) = flow { + private fun restore( + token: Long, + packageName: String, + metadata: PackageMetadata, + installResult: MutableInstallResult + ) = flow { // create a cache file to write the APK into val cachedApk = File.createTempFile(packageName, ".apk", context.cacheDir) // copy APK to cache file and calculate SHA-256 hash while we are at it @@ -97,7 +102,7 @@ internal class ApkRestore( // parse APK (GET_SIGNATURES is needed even though deprecated) @Suppress("DEPRECATION") val flags = GET_SIGNING_CERTIFICATES or GET_SIGNATURES val packageInfo = pm.getPackageArchiveInfo(cachedApk.absolutePath, flags) - ?: throw IOException("getPackageArchiveInfo returned null") + ?: throw IOException("getPackageArchiveInfo returned null") // check APK package name if (packageName != packageInfo.packageName) { @@ -106,7 +111,10 @@ internal class ApkRestore( // check APK version code if (metadata.version != packageInfo.longVersionCode) { - Log.w(TAG, "Package $packageName expects version code ${metadata.version}, but has ${packageInfo.longVersionCode}.") + Log.w( + TAG, "Package $packageName expects version code ${metadata.version}," + + "but has ${packageInfo.longVersionCode}." + ) // TODO should we let this one pass, maybe once we can revert PackageMetadata during backup? } @@ -125,7 +133,13 @@ internal class ApkRestore( val icon = appInfo.loadIcon(pm) val name = pm.getApplicationLabel(appInfo) - installResult.update(packageName) { it.copy(status = IN_PROGRESS, name = name, icon = icon) } + installResult.update(packageName) { result -> + result.copy( + status = IN_PROGRESS, + name = name, + icon = icon + ) + } emit(installResult) // ensure system apps are actually installed and newer system apps as well @@ -143,9 +157,10 @@ internal class ApkRestore( } // install APK and emit updates from it - apkInstaller.install(cachedApk, packageName, metadata.installer, installResult).collect { result -> - emit(result) - } + apkInstaller.install(cachedApk, packageName, metadata.installer, installResult) + .collect { result -> + emit(result) + } } private fun fail(installResult: MutableInstallResult, packageName: String): InstallResult { @@ -163,8 +178,12 @@ internal fun InstallResult.getInProgress(): ApkRestoreResult? { return filtered.values.first() } -internal class MutableInstallResult(initialCapacity: Int) : ConcurrentHashMap(initialCapacity) { - fun update(packageName: String, updateFun: (ApkRestoreResult) -> ApkRestoreResult): MutableInstallResult { +internal class MutableInstallResult(initialCapacity: Int) : + ConcurrentHashMap(initialCapacity) { + fun update( + packageName: String, + updateFun: (ApkRestoreResult) -> ApkRestoreResult + ): MutableInstallResult { val result = get(packageName) check(result != null) { "ApkRestoreResult for $packageName does not exist." } set(packageName, updateFun(result)) @@ -173,12 +192,12 @@ internal class MutableInstallResult(initialCapacity: Int) : ConcurrentHashMap { override fun compareTo(other: ApkRestoreResult): Int { return other.progress.compareTo(progress) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt index bc26ba19..5f90d7db 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt @@ -16,19 +16,21 @@ import java.io.IOException import java.io.InputStream private class FullRestoreState( - internal val token: Long, - internal val packageInfo: PackageInfo) { - internal var inputStream: InputStream? = null + val token: Long, + val packageInfo: PackageInfo +) { + var inputStream: InputStream? = null } private val TAG = FullRestore::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") internal class FullRestore( - private val plugin: FullRestorePlugin, - private val outputFactory: OutputFactory, - private val headerReader: HeaderReader, - private val crypto: Crypto) { + private val plugin: FullRestorePlugin, + private val outputFactory: OutputFactory, + private val headerReader: HeaderReader, + private val crypto: Crypto +) { private var state: FullRestoreState? = null diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index 04f11b3e..4c34a67d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -19,12 +19,12 @@ import java.util.ArrayList import javax.crypto.AEADBadTagException private class KVRestoreState( - internal val token: Long, - internal val packageInfo: PackageInfo, + val token: Long, + val packageInfo: PackageInfo, /** * Optional [PackageInfo] for single package restore, optimizes restore of @pm@ */ - internal val pmPackageInfo: PackageInfo? + val pmPackageInfo: PackageInfo? ) private val TAG = KVRestore::class.java.simpleName @@ -156,8 +156,8 @@ internal class KVRestore( Unit } - private class DecodedKey(internal val base64Key: String) : Comparable { - internal val key = base64Key.decodeBase64() + private class DecodedKey(val base64Key: String) : Comparable { + val key = base64Key.decodeBase64() override fun compareTo(other: DecodedKey) = key.compareTo(other.key) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestorePlugin.kt index fa2b0ae0..6cdd18c5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestorePlugin.kt @@ -30,6 +30,10 @@ interface KVRestorePlugin { * Note: Implementations might expect that you call [hasDataForPackage] before. */ @Throws(IOException::class) - suspend fun getInputStreamForRecord(token: Long, packageInfo: PackageInfo, key: String): InputStream + suspend fun getInputStreamForRecord( + token: Long, + packageInfo: PackageInfo, + key: String + ): InputStream } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt index e196b9a4..393cbfee 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt @@ -13,7 +13,7 @@ internal class OutputFactory { fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput { return BackupDataOutput(outputFileDescriptor.fileDescriptor) } - + fun getOutputStream(outputFileDescriptor: ParcelFileDescriptor): OutputStream { return FileOutputStream(outputFileDescriptor.fileDescriptor) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt index 7d51c7f7..acb56a0b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt @@ -20,7 +20,7 @@ abstract class BackupActivity : AppCompatActivity() { protected fun showFragment(f: Fragment, addToBackStack: Boolean = false) { val fragmentTransaction = supportFragmentManager.beginTransaction() - .replace(R.id.fragment, f) + .replace(R.id.fragment, f) if (addToBackStack) fragmentTransaction.addToBackStack(null) fragmentTransaction.commit() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt index e1444dca..7dac0f91 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt @@ -23,7 +23,8 @@ open class LiveEvent : LiveData>() { } } - internal class LiveEventObserver(private val handler: LiveEventHandler) : Observer> { + internal class LiveEventObserver(private val handler: LiveEventHandler) : + Observer> { override fun onChanged(consumableEvent: ConsumableEvent?) { if (consumableEvent != null) { val content = consumableEvent.contentIfNotConsumed diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt index 6527fd2f..759e82b5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt @@ -7,9 +7,9 @@ import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.storage.StorageViewModel abstract class RequireProvisioningViewModel( - protected val app: Application, - protected val settingsManager: SettingsManager, - private val keyManager: KeyManager + protected val app: Application, + protected val settingsManager: SettingsManager, + private val keyManager: KeyManager ) : AndroidViewModel(app) { abstract val isRestoreOperation: Boolean diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt index 4fffe435..bca1f7ca 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt @@ -49,14 +49,14 @@ class RecoveryCodeActivity : BackupActivity() { private fun showOutput() { supportFragmentManager.beginTransaction() - .add(R.id.fragment, RecoveryCodeOutputFragment(), "Code") - .commit() + .add(R.id.fragment, RecoveryCodeOutputFragment(), "Code") + .commit() } private fun showInput(addToBackStack: Boolean) { val tag = "Confirm" val fragmentTransaction = supportFragmentManager.beginTransaction() - .replace(R.id.fragment, RecoveryCodeInputFragment(), tag) + .replace(R.id.fragment, RecoveryCodeInputFragment(), tag) if (addToBackStack) fragmentTransaction.addToBackStack(tag) fragmentTransaction.commit() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeAdapter.kt index bb7b276d..606e024b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeAdapter.kt @@ -8,11 +8,12 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.Adapter import com.stevesoltys.seedvault.R -class RecoveryCodeAdapter(private val items: List) : Adapter() { +class RecoveryCodeAdapter(private val items: List) : + Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecoveryCodeViewHolder { val v = LayoutInflater.from(parent.context) - .inflate(R.layout.list_item_recovery_code_output, parent, false) as View + .inflate(R.layout.list_item_recovery_code_output, parent, false) as View return RecoveryCodeViewHolder(v) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt index dce630cf..60e4c673 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt @@ -130,7 +130,8 @@ class RecoveryCodeInputFragment : Fragment() { private fun showWrongWordError(input: List, e: WordNotFoundException) { val i = input.indexOf(e.word) if (i == -1) throw AssertionError() - showError(i, getString(R.string.recovery_code_error_invalid_word, e.suggestion1, e.suggestion2)) + val str = getString(R.string.recovery_code_error_invalid_word, e.suggestion1, e.suggestion2) + showError(i, str) } private fun showError(i: Int, errorMsg: CharSequence) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt index c1372a9a..e5b3c151 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt @@ -15,9 +15,10 @@ import java.io.IOException private val TAG = RestoreStorageViewModel::class.java.simpleName internal class RestoreStorageViewModel( - private val app: Application, - private val restorePlugin: RestorePlugin, - settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) { + private val app: Application, + private val restorePlugin: RestorePlugin, + settingsManager: SettingsManager +) : StorageViewModel(app, settingsManager) { override val isRestoreOperation = true @@ -37,7 +38,8 @@ internal class RestoreStorageViewModel( Log.w(TAG, "Location was rejected: $uri") // notify the UI that the location was invalid - val errorMsg = app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT) + val errorMsg = + app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT) mLocationChecked.postEvent(LocationResult(errorMsg)) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt index be1f34e0..10350ea2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt @@ -63,10 +63,10 @@ class StorageActivity : BackupActivity() { if (viewModel.isRestoreOperation) { supportFragmentManager.popBackStack() AlertDialog.Builder(this) - .setTitle(getString(R.string.restore_invalid_location_title)) - .setMessage(errorMsg) - .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } - .show() + .setTitle(getString(R.string.restore_invalid_location_title)) + .setMessage(errorMsg) + .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() } else { showFragment(StorageCheckFragment.newInstance(getCheckFragmentTitle(), errorMsg)) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt index c9714ba7..b4fbf3d8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt @@ -17,14 +17,15 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.ui.storage.StorageRootAdapter.StorageRootViewHolder internal class StorageRootAdapter( - private val isRestore: Boolean, - private val listener: StorageRootClickedListener) : Adapter() { + private val isRestore: Boolean, + private val listener: StorageRootClickedListener +) : Adapter() { private val items = ArrayList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StorageRootViewHolder { val v = LayoutInflater.from(parent.context) - .inflate(R.layout.list_item_storage_root, parent, false) as View + .inflate(R.layout.list_item_storage_root, parent, false) as View return StorageRootViewHolder(v) } @@ -84,16 +85,16 @@ internal class StorageRootAdapter( private fun showWarningDialog(context: Context, item: StorageRoot) { AlertDialog.Builder(context) - .setTitle(R.string.storage_internal_warning_title) - .setMessage(R.string.storage_internal_warning_message) - .setPositiveButton(R.string.storage_internal_warning_choose_other) { dialog, _ -> - dialog.dismiss() - } - .setNegativeButton(R.string.storage_internal_warning_use_anyway) { dialog, _ -> - dialog.dismiss() - listener.onClick(item) - } - .show() + .setTitle(R.string.storage_internal_warning_title) + .setMessage(R.string.storage_internal_warning_message) + .setPositiveButton(R.string.storage_internal_warning_choose_other) { dialog, _ -> + dialog.dismiss() + } + .setNegativeButton(R.string.storage_internal_warning_use_anyway) { dialog, _ -> + dialog.dismiss() + listener.onClick(item) + } + .show() } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt index 1ef6a385..c8936e12 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt @@ -42,16 +42,17 @@ private const val NEXTCLOUD_PACKAGE = "com.nextcloud.client" private const val NEXTCLOUD_ACTIVITY = "com.owncloud.android.authentication.AuthenticatorActivity" data class StorageRoot( - internal val authority: String, - internal val rootId: String, - internal val documentId: String, - internal val icon: Drawable?, - internal val title: String, - internal val summary: String?, - internal val availableBytes: Long?, - internal val isUsb: Boolean, - internal val enabled: Boolean = true, - internal val overrideClickListener: (() -> Unit)? = null) { + internal val authority: String, + internal val rootId: String, + internal val documentId: String, + internal val icon: Drawable?, + internal val title: String, + internal val summary: String?, + internal val availableBytes: Long?, + internal val isUsb: Boolean, + internal val enabled: Boolean = true, + internal val overrideClickListener: (() -> Unit)? = null +) { internal val uri: Uri by lazy { DocumentsContract.buildTreeDocumentUri(authority, documentId) @@ -70,7 +71,8 @@ internal class StorageRootFetcher(private val context: Context, private val isRe private val packageManager = context.packageManager private val contentResolver = context.contentResolver - private val whitelistedAuthorities = context.resources.getStringArray(R.array.storage_authority_whitelist) + private val whitelistedAuthorities = + context.resources.getStringArray(R.array.storage_authority_whitelist) private var listener: RemovableStorageListener? = null private val handler = Handler(Looper.getMainLooper()) @@ -143,17 +145,17 @@ internal class StorageRootFetcher(private val context: Context, private val isRe val rootId = cursor.getString(COLUMN_ROOT_ID)!! if (authority == AUTHORITY_STORAGE && rootId == ROOT_ID_HOME) return null return StorageRoot( - authority = authority, - rootId = rootId, - documentId = cursor.getString(COLUMN_DOCUMENT_ID)!!, - icon = getIcon(context, authority, rootId, cursor.getInt(COLUMN_ICON)), - title = cursor.getString(COLUMN_TITLE)!!, - summary = cursor.getString(COLUMN_SUMMARY), - availableBytes = cursor.getLong(COLUMN_AVAILABLE_BYTES).let { bytes -> - // AOSP 11 reports -1 instead of null - if (bytes == -1L) null else bytes - }, - isUsb = flags and FLAG_REMOVABLE_USB != 0 + authority = authority, + rootId = rootId, + documentId = cursor.getString(COLUMN_DOCUMENT_ID)!!, + icon = getIcon(context, authority, rootId, cursor.getInt(COLUMN_ICON)), + title = cursor.getString(COLUMN_TITLE)!!, + summary = cursor.getString(COLUMN_SUMMARY), + availableBytes = cursor.getLong(COLUMN_AVAILABLE_BYTES).let { bytes -> + // AOSP 11 reports -1 instead of null + if (bytes == -1L) null else bytes + }, + isUsb = flags and FLAG_REMOVABLE_USB != 0 ) } @@ -165,15 +167,15 @@ internal class StorageRootFetcher(private val context: Context, private val isRe if (root.authority == AUTHORITY_STORAGE && root.isUsb) return } val root = StorageRoot( - authority = AUTHORITY_STORAGE, - rootId = "usb", - documentId = "fake", - icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0), - title = context.getString(R.string.storage_fake_drive_title), - summary = context.getString(R.string.storage_fake_drive_summary), - availableBytes = null, - isUsb = true, - enabled = false + authority = AUTHORITY_STORAGE, + rootId = "usb", + documentId = "fake", + icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0), + title = context.getString(R.string.storage_fake_drive_title), + summary = context.getString(R.string.storage_fake_drive_summary), + availableBytes = null, + isUsb = true, + enabled = false ) roots.add(root) } @@ -206,24 +208,24 @@ internal class StorageRootFetcher(private val context: Context, private val isRe else R.string.storage_fake_nextcloud_summary_unavailable } else R.string.storage_fake_nextcloud_summary val root = StorageRoot( - authority = AUTHORITY_NEXTCLOUD, - rootId = "fake", - documentId = "fake", - icon = getIcon(context, AUTHORITY_NEXTCLOUD, "fake", 0), - title = context.getString(R.string.storage_fake_nextcloud_title), - summary = context.getString(summaryRes), - availableBytes = null, - isUsb = false, - enabled = !isInstalled || isRestore, - overrideClickListener = { - if (isInstalled) context.startActivity(intent) - else { - val uri = Uri.parse("market://details?id=$NEXTCLOUD_PACKAGE") - val i = Intent(ACTION_VIEW, uri) - i.addFlags(FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) - } + authority = AUTHORITY_NEXTCLOUD, + rootId = "fake", + documentId = "fake", + icon = getIcon(context, AUTHORITY_NEXTCLOUD, "fake", 0), + title = context.getString(R.string.storage_fake_nextcloud_title), + summary = context.getString(summaryRes), + availableBytes = null, + isUsb = false, + enabled = !isInstalled || isRestore, + overrideClickListener = { + if (isInstalled) context.startActivity(intent) + else { + val uri = Uri.parse("market://details?id=$NEXTCLOUD_PACKAGE") + val i = Intent(ACTION_VIEW, uri) + i.addFlags(FLAG_ACTIVITY_NEW_TASK) + context.startActivity(i) } + } ) roots.add(root) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt index 5f5b2a54..70c79825 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt @@ -123,7 +123,7 @@ private class OpenSeedvaultTree : OpenDocumentTree() { check(input != null) { "Uri was null, but is needed." } data = input val flags = FLAG_GRANT_PERSISTABLE_URI_PERMISSION or - FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION + FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION addFlags(flags) } } diff --git a/app/src/main/res/drawable/nextcloud.xml b/app/src/main/res/drawable/nextcloud.xml index e0491076..ea599210 100644 --- a/app/src/main/res/drawable/nextcloud.xml +++ b/app/src/main/res/drawable/nextcloud.xml @@ -1,5 +1,4 @@ - - - - + + diff --git a/app/src/main/res/drawable/nextcloud_background.xml b/app/src/main/res/drawable/nextcloud_background.xml index a9025ed7..7cab82aa 100644 --- a/app/src/main/res/drawable/nextcloud_background.xml +++ b/app/src/main/res/drawable/nextcloud_background.xml @@ -18,1205 +18,1205 @@ License along with this program. If not, see . --> + android:width="108dp" + android:height="108dp" + android:viewportWidth="1344" + android:viewportHeight="1344"> + android:fillColor="#0082C9" + android:pathData="M-30.5,-19.2h1488.2v1362.7h-1488.2z" /> + android:pathData="M1296.5,360.1C1304.7,360.1 1311.3,353.4 1311.3,345.2C1311.3,337 1304.7,330.3 1296.5,330.3C1288.2,330.3 1281.6,337 1281.6,345.2C1281.6,353.4 1288.2,360.1 1296.5,360.1Z" /> + android:pathData="M1345.2,360.1C1353.4,360.1 1360.1,353.4 1360.1,345.2C1360.1,337 1353.4,330.3 1345.2,330.3C1337,330.3 1330.3,337 1330.3,345.2C1330.3,353.4 1337,360.1 1345.2,360.1Z" /> + android:pathData="M1296.5,408.3C1304.7,408.3 1311.3,401.7 1311.3,393.5C1311.3,385.2 1304.7,378.6 1296.5,378.6C1288.2,378.6 1281.6,385.2 1281.6,393.5C1281.6,401.7 1288.2,408.3 1296.5,408.3Z" /> + android:pathData="M1345.2,408.3C1353.4,408.3 1360.1,401.7 1360.1,393.5C1360.1,385.2 1353.4,378.6 1345.2,378.6C1337,378.6 1330.3,385.2 1330.3,393.5C1330.3,401.7 1337,408.3 1345.2,408.3Z" /> + android:pathData="M1103,457.1C1111.8,457.1 1118.4,450.4 1118.4,441.7C1118.4,433.5 1111.8,426.3 1103,426.3C1094.8,426.3 1087.6,433 1087.6,441.7C1087.6,450.4 1094.8,457.1 1103,457.1Z" /> + android:pathData="M1200,456.6C1208.2,456.6 1214.9,449.9 1214.9,441.7C1214.9,433.5 1208.2,426.8 1200,426.8C1191.8,426.8 1185.1,433.5 1185.1,441.7C1185.1,449.9 1191.8,456.6 1200,456.6Z" /> + android:pathData="M1248.2,459.6C1258,459.6 1265.7,451.9 1265.7,442.2C1265.7,432.4 1258,424.7 1248.2,424.7C1238.5,424.7 1230.8,432.4 1230.8,442.2C1230.8,451.4 1238.5,459.6 1248.2,459.6Z" /> + android:pathData="M1296.5,457.1C1304.7,457.1 1311.8,450.4 1311.8,442.2C1311.8,434 1305.2,427.3 1296.5,427.3C1288.2,427.3 1281.6,434 1281.6,442.2C1281.6,450.4 1288.2,457.1 1296.5,457.1Z" /> + android:pathData="M1345.2,457.1C1353.9,457.1 1360.6,450.4 1360.6,441.7C1360.6,433.5 1353.9,426.3 1345.2,426.3C1336.5,426.3 1329.8,433 1329.8,441.7C1329.8,450.4 1336.5,457.1 1345.2,457.1Z" /> + android:pathData="M1054.3,505.3C1062.5,505.3 1069.2,498.6 1069.2,490.4C1069.2,482.2 1062.5,475.5 1054.3,475.5C1046.1,475.5 1039.4,482.2 1039.4,490.4C1039.9,498.6 1046.6,505.3 1054.3,505.3Z" /> + android:pathData="M1103,505.3C1111.8,505.3 1118.4,498.6 1118.4,489.9C1118.4,481.7 1111.8,474.5 1103,474.5C1094.8,474.5 1087.6,481.2 1087.6,489.9C1087.6,498.6 1094.8,505.3 1103,505.3Z" /> + android:pathData="M1200,505.3C1208.2,505.3 1214.9,498.6 1214.9,490.4C1214.9,482.2 1208.2,475.5 1200,475.5C1191.8,475.5 1185.1,482.2 1185.1,490.4C1185.1,498.6 1191.8,505.3 1200,505.3Z" /> + android:pathData="M1248.2,505.3C1256.4,505.3 1263.1,498.6 1263.1,490.4C1263.1,482.2 1256.4,475.5 1248.2,475.5C1240,475.5 1233.3,482.2 1233.3,490.4C1233.3,498.6 1240,505.3 1248.2,505.3Z" /> + android:pathData="M1345.2,505.3C1353.9,505.3 1360.6,498.6 1360.6,489.9C1360.6,481.7 1353.9,474.5 1345.2,474.5C1336.5,474.5 1329.8,481.2 1329.8,489.9C1329.8,498.6 1336.5,505.3 1345.2,505.3Z" /> + android:pathData="M1200,553.5C1208.2,553.5 1214.9,546.9 1214.9,538.6C1214.9,530.4 1208.2,523.8 1200,523.8C1191.8,523.8 1185.1,530.4 1185.1,538.6C1185.1,546.9 1191.8,553.5 1200,553.5Z" /> + android:pathData="M1103,601.8C1111.2,601.8 1117.9,595.1 1117.9,586.9C1117.9,578.7 1111.2,572 1103,572C1094.8,572 1088.2,578.7 1088.2,586.9C1088.2,595.1 1094.8,601.8 1103,601.8Z" /> + android:pathData="M1151.3,602.3C1159.5,602.3 1166.1,595.6 1166.1,587.4C1166.1,579.2 1159.5,572.5 1151.3,572.5C1143,572.5 1135.9,579.2 1135.9,587.4C1136.4,595.6 1143,602.3 1151.3,602.3Z" /> + android:pathData="M1200,602.8C1208.7,602.8 1215.9,595.6 1215.9,586.9C1215.9,578.2 1208.7,571 1200,571C1191.3,571 1184.1,578.2 1184.1,586.9C1184.1,596.1 1190.8,602.8 1200,602.8Z" /> + android:pathData="M1248.2,604.3C1258,604.3 1265.7,596.6 1265.7,586.9C1265.7,577.6 1258,569.4 1248.2,569.4C1239,569.4 1231.3,577.1 1231.3,586.9C1230.8,596.6 1238.5,604.3 1248.2,604.3Z" /> + android:pathData="M1296.5,606.4C1307.2,606.4 1315.4,597.6 1315.4,587.4C1315.4,576.6 1306.7,568.4 1296.5,568.4C1286.2,568.4 1277.5,577.1 1277.5,587.4C1277.5,597.6 1286.2,606.4 1296.5,606.4Z" /> + android:pathData="M1345.2,601.8C1353.4,601.8 1360.1,595.1 1360.1,586.9C1360.1,578.7 1353.4,572 1345.2,572C1337,572 1330.3,578.7 1330.3,586.9C1330.3,595.1 1337,601.8 1345.2,601.8Z" /> + android:pathData="M1054.3,650.5C1062.5,650.5 1069.2,643.8 1069.2,635.6C1069.2,627.4 1062.5,620.7 1054.3,620.7C1046.1,620.7 1039.4,627.4 1039.4,635.6C1039.9,643.8 1046.6,650.5 1054.3,650.5Z" /> + android:pathData="M1103,651C1111.8,651 1118.4,644.3 1118.4,635.6C1118.4,627.4 1111.8,620.2 1103,620.2C1094.8,620.2 1087.6,626.9 1087.6,635.6C1087.6,643.8 1094.8,651 1103,651Z" /> + android:pathData="M1151.3,651C1159.5,651 1166.6,644.3 1166.6,635.6C1166.6,627.4 1160,620.2 1151.3,620.2C1142.5,620.2 1135.9,626.9 1135.9,635.6C1136.4,643.8 1143,651 1151.3,651Z" /> + android:pathData="M1200,654.6C1210.3,654.6 1219,645.9 1219,635.6C1219,625.4 1210.3,616.6 1200,616.6C1189.7,616.6 1181,625.4 1181,635.6C1180.5,645.9 1189.2,654.6 1200,654.6Z" /> + android:pathData="M1248.2,650.5C1256.4,650.5 1263.1,643.8 1263.1,635.6C1263.1,627.4 1256.4,620.7 1248.2,620.7C1240,620.7 1233.3,627.4 1233.3,635.6C1233.3,643.8 1240,650.5 1248.2,650.5Z" /> + android:pathData="M1296.5,653.1C1306.2,653.1 1313.9,645.4 1313.9,635.6C1313.9,625.9 1306.2,618.2 1296.5,618.2C1286.7,618.2 1279,625.9 1279,635.6C1279,645.4 1286.7,653.1 1296.5,653.1Z" /> + android:pathData="M1345.2,656.1C1356.5,656.1 1365.7,646.9 1365.7,635.6C1365.7,624.3 1356.5,615.1 1345.2,615.1C1333.9,615.1 1324.7,624.3 1324.7,635.6C1324.7,646.9 1333.4,656.1 1345.2,656.1Z" /> + android:pathData="M1054.3,702.3C1064.6,702.3 1072.8,694.1 1072.8,683.8C1072.8,673.6 1064.6,665.4 1054.3,665.4C1044,665.4 1035.8,673.6 1035.8,683.8C1036.3,694.1 1044.5,702.3 1054.3,702.3Z" /> + android:pathData="M1103,699.2C1111.8,699.2 1118.4,692.6 1118.4,683.8C1118.4,675.6 1111.8,668.4 1103,668.4C1094.8,668.4 1087.6,675.1 1087.6,683.8C1087.6,692.6 1094.8,699.2 1103,699.2Z" /> + android:pathData="M1151.3,699.2C1159.5,699.2 1166.6,692.6 1166.6,683.8C1166.6,675.6 1160,668.4 1151.3,668.4C1142.5,668.4 1135.9,675.1 1135.9,683.8C1136.4,692.6 1143,699.2 1151.3,699.2Z" /> + android:pathData="M1200,702.8C1210.3,702.8 1219,694.1 1219,683.8C1219,673.6 1210.3,664.9 1200,664.9C1189.7,664.9 1181,673.6 1181,683.8C1180.5,694.6 1189.2,702.8 1200,702.8Z" /> + android:pathData="M1248.2,698.7C1256.4,698.7 1263.1,692 1263.1,683.8C1263.1,675.6 1256.4,669 1248.2,669C1240,669 1233.3,675.6 1233.3,683.8C1233.3,692 1240,698.7 1248.2,698.7Z" /> + android:pathData="M1296.5,701.3C1306.2,701.3 1313.9,693.6 1313.9,683.8C1313.9,674.1 1306.2,666.4 1296.5,666.4C1286.7,666.4 1279,674.1 1279,683.8C1279,693.6 1286.7,701.3 1296.5,701.3Z" /> + android:pathData="M1345.2,698.7C1353.4,698.7 1360.1,692 1360.1,683.8C1360.1,675.6 1353.4,669 1345.2,669C1337,669 1330.3,675.6 1330.3,683.8C1330.3,692 1337,698.7 1345.2,698.7Z" /> + android:pathData="M1054.3,746.9C1062.5,746.9 1069.2,740.3 1069.2,732.1C1069.2,723.9 1062.5,717.2 1054.3,717.2C1046.1,717.2 1039.4,723.9 1039.4,732.1C1039.9,740.8 1046.6,746.9 1054.3,746.9Z" /> + android:pathData="M1103,746.9C1111.2,746.9 1117.9,740.3 1117.9,732.1C1117.9,723.9 1111.2,717.2 1103,717.2C1094.8,717.2 1088.2,723.9 1088.2,732.1C1088.2,740.8 1094.8,746.9 1103,746.9Z" /> + android:pathData="M1151.3,746.9C1159.5,746.9 1166.1,740.3 1166.1,732.1C1166.1,723.9 1159.5,717.2 1151.3,717.2C1143,717.2 1136.4,723.9 1136.4,732.1C1136.4,740.8 1143,746.9 1151.3,746.9Z" /> + android:pathData="M1200,747.5C1208.2,747.5 1215.4,740.8 1215.4,732.1C1215.4,723.3 1208.7,716.7 1200,716.7C1191.3,716.7 1184.6,723.3 1184.6,732.1C1184.6,740.8 1191.3,747.5 1200,747.5Z" /> + android:pathData="M1248.2,749.5C1258,749.5 1265.7,741.8 1265.7,732.6C1265.7,722.8 1258,715.1 1248.2,715.1C1239,715.1 1231.3,722.8 1231.3,732.6C1230.8,741.8 1238.5,749.5 1248.2,749.5Z" /> + android:pathData="M1296.5,746.9C1304.7,746.9 1311.3,740.3 1311.3,732.1C1311.3,723.9 1304.7,717.2 1296.5,717.2C1288.2,717.2 1281.6,723.9 1281.6,732.1C1281.6,740.8 1288.2,746.9 1296.5,746.9Z" /> + android:pathData="M1345.2,746.9C1353.4,746.9 1360.1,740.3 1360.1,732.1C1360.1,723.9 1353.4,717.2 1345.2,717.2C1337,717.2 1330.3,723.9 1330.3,732.1C1330.3,740.8 1337,746.9 1345.2,746.9Z" /> + android:pathData="M812.6,795.7C820.8,795.7 827.5,789 827.5,780.8C827.5,772.6 820.8,765.9 812.6,765.9C804.4,765.9 797.8,772.6 797.8,780.8C797.8,789 804.4,795.7 812.6,795.7Z" /> + android:pathData="M860.9,799.8C871.6,799.8 879.9,791.1 879.9,780.8C879.9,770 871.1,761.8 860.9,761.8C850.6,761.8 841.9,770.5 841.9,780.8C841.9,791.1 850.6,799.8 860.9,799.8Z" /> + android:pathData="M957.8,795.7C966,795.7 972.7,789 972.7,780.8C972.7,772.6 966,765.9 957.8,765.9C949.6,765.9 943,772.6 943,780.8C943,789 949.6,795.7 957.8,795.7Z" /> + android:pathData="M1006.1,799.3C1016.3,799.3 1024.5,791.1 1024.5,780.8C1024.5,770.5 1016.3,762.3 1006.1,762.3C995.8,762.3 987.6,770.5 987.6,780.8C987.6,791.1 995.8,799.3 1006.1,799.3Z" /> + android:pathData="M1103,795.7C1111.2,795.7 1117.9,789 1117.9,780.8C1117.9,772.6 1111.2,765.9 1103,765.9C1094.8,765.9 1088.2,772.6 1088.2,780.8C1088.2,789 1094.8,795.7 1103,795.7Z" /> + android:pathData="M1151.3,795.7C1159.5,795.7 1166.1,789 1166.1,780.8C1166.1,772.6 1159.5,765.9 1151.3,765.9C1143,765.9 1136.4,772.6 1136.4,780.8C1136.4,789 1143,795.7 1151.3,795.7Z" /> + android:pathData="M1248.2,795.7C1256.4,795.7 1263.1,789 1263.1,780.8C1263.1,772.6 1256.4,765.9 1248.2,765.9C1240,765.9 1233.3,772.6 1233.3,780.8C1233.3,789 1240,795.7 1248.2,795.7Z" /> + android:pathData="M1296.5,795.7C1304.7,795.7 1311.3,789 1311.3,780.8C1311.3,772.6 1304.7,765.9 1296.5,765.9C1288.2,765.9 1281.6,772.6 1281.6,780.8C1281.6,789 1288.2,795.7 1296.5,795.7Z" /> + android:pathData="M1345.2,800.8C1356.5,800.8 1365.2,792.1 1365.2,780.8C1365.2,770 1356.5,760.8 1345.2,760.8C1333.9,760.8 1325.2,769.5 1325.2,780.8C1325.2,791.6 1333.9,800.8 1345.2,800.8Z" /> + android:pathData="M812.6,843.9C820.8,843.9 827.5,837.2 827.5,829C827.5,820.8 820.8,814.2 812.6,814.2C804.4,814.2 797.8,820.8 797.8,829C797.8,837.2 804.4,843.9 812.6,843.9Z" /> + android:pathData="M860.9,848.5C871.6,848.5 879.9,839.8 879.9,829.5C879.9,818.8 871.1,810.6 860.9,810.6C850.6,810.6 841.9,819.3 841.9,829.5C841.9,839.8 850.6,848.5 860.9,848.5Z" /> + android:pathData="M1006.1,843.9C1014.3,843.9 1020.9,837.2 1020.9,829C1020.9,820.8 1014.3,814.2 1006.1,814.2C997.9,814.2 991.2,820.8 991.2,829C991.2,837.2 997.9,843.9 1006.1,843.9Z" /> + android:pathData="M1103,843.9C1111.2,843.9 1117.9,837.2 1117.9,829C1117.9,820.8 1111.2,814.2 1103,814.2C1094.8,814.2 1088.2,820.8 1088.2,829C1088.2,837.2 1094.8,843.9 1103,843.9Z" /> + android:pathData="M1151.3,846.5C1161,846.5 1168.7,838.8 1168.7,829C1168.7,819.3 1161,811.6 1151.3,811.6C1141.5,811.6 1133.8,819.3 1133.8,829C1133.8,838.8 1141.5,846.5 1151.3,846.5Z" /> + android:pathData="M1200,843.9C1208.2,843.9 1214.9,837.2 1214.9,829C1214.9,820.8 1208.2,814.2 1200,814.2C1191.8,814.2 1185.1,820.8 1185.1,829C1185.1,837.2 1191.8,843.9 1200,843.9Z" /> + android:pathData="M1248.2,843.9C1256.4,843.9 1263.1,837.2 1263.1,829C1263.1,820.8 1256.4,814.2 1248.2,814.2C1240,814.2 1233.3,820.8 1233.3,829C1233.3,837.2 1240,843.9 1248.2,843.9Z" /> + android:pathData="M1296.5,844.4C1304.7,844.4 1311.8,837.8 1311.8,829.5C1311.8,821.3 1305.2,814.7 1296.5,814.7C1288.2,814.7 1281.6,821.3 1281.6,829.5C1281.6,837.2 1288.2,844.4 1296.5,844.4Z" /> + android:pathData="M1345.2,843.9C1353.4,843.9 1360.1,837.2 1360.1,829C1360.1,820.8 1353.4,814.2 1345.2,814.2C1337,814.2 1330.3,820.8 1330.3,829C1330.3,837.2 1337,843.9 1345.2,843.9Z" /> + android:pathData="M812.6,892.1C820.8,892.1 827.5,885.5 827.5,877.3C827.5,869.1 820.8,862.4 812.6,862.4C804.4,862.4 797.8,869.1 797.8,877.3C797.8,885.5 804.4,892.1 812.6,892.1Z" /> + android:pathData="M909.1,892.1C917.3,892.1 924,885.5 924,877.3C924,869.1 917.3,862.4 909.1,862.4C900.9,862.4 894.2,869.1 894.2,877.3C894.7,885.5 901.4,892.1 909.1,892.1Z" /> + android:pathData="M957.8,895.7C967.6,895.7 975.8,887.5 975.8,877.8C975.8,868 967.6,859.8 957.8,859.8C947.6,859.8 939.9,868 939.9,877.8C939.9,887.5 947.6,895.7 957.8,895.7Z" /> + android:pathData="M1006.1,892.1C1014.3,892.1 1020.9,885.5 1020.9,877.3C1020.9,869.1 1014.3,862.4 1006.1,862.4C997.9,862.4 991.2,869.1 991.2,877.3C991.7,885.5 997.9,892.1 1006.1,892.1Z" /> + android:pathData="M1054.3,892.1C1062.5,892.1 1069.2,885.5 1069.2,877.3C1069.2,869.1 1062.5,862.4 1054.3,862.4C1046.1,862.4 1039.4,869.1 1039.4,877.3C1039.9,885.5 1046.6,892.1 1054.3,892.1Z" /> + android:pathData="M1103,894.7C1112.3,894.7 1120,887 1120,877.8C1120,868.5 1112.3,860.8 1103,860.8C1093.8,860.8 1086.1,868.5 1086.1,877.8C1086.1,887 1093.3,894.7 1103,894.7Z" /> + android:pathData="M1151.3,894.7C1160.5,894.7 1168.2,887 1168.2,877.8C1168.2,868.5 1160.5,860.8 1151.3,860.8C1142,860.8 1134.3,868.5 1134.3,877.8C1134.3,887 1142,894.7 1151.3,894.7Z" /> + android:pathData="M1200,894.7C1209.2,894.7 1216.9,887 1216.9,877.3C1216.9,868 1209.2,860.3 1200,860.3C1190.2,860.3 1182.6,868 1182.6,877.3C1182.6,887 1190.2,894.7 1200,894.7Z" /> + android:pathData="M1248.2,894.7C1258,894.7 1265.7,887 1265.7,877.3C1265.7,868 1258,860.3 1248.2,860.3C1239,860.3 1231.3,868 1231.3,877.3C1230.8,887 1238.5,894.7 1248.2,894.7Z" /> + android:pathData="M1296.5,895.2C1306.2,895.2 1313.9,887.5 1313.9,877.8C1313.9,868 1306.2,860.3 1296.5,860.3C1286.7,860.3 1279,868 1279,877.8C1279,887 1286.7,895.2 1296.5,895.2Z" /> + android:pathData="M1345.2,892.1C1353.4,892.1 1360.1,885.5 1360.1,877.3C1360.1,869.1 1353.4,862.4 1345.2,862.4C1337,862.4 1330.3,869.1 1330.3,877.3C1330.3,885.5 1337,892.1 1345.2,892.1Z" /> + android:pathData="M522.3,940.9C530.5,940.9 537.1,934.2 537.1,926C537.1,917.8 530.5,911.1 522.3,911.1C514,911.1 507.4,917.8 507.4,926C507.4,934.2 514,940.9 522.3,940.9Z" /> + android:pathData="M570.5,940.9C578.7,940.9 585.4,934.2 585.4,926C585.4,917.8 578.7,911.1 570.5,911.1C562.3,911.1 555.6,917.8 555.6,926C555.6,934.2 562.3,940.9 570.5,940.9Z" /> + android:pathData="M619.2,940.9C627.4,940.9 634.1,934.2 634.1,926C634.1,917.8 627.4,911.1 619.2,911.1C611,911.1 604.3,917.8 604.3,926C604.3,934.2 611,940.9 619.2,940.9Z" /> + android:pathData="M667.4,942.4C676.7,942.4 683.9,935.2 683.9,926C683.9,916.8 676.2,909.6 667.4,909.6C658.2,909.6 651,916.8 651,926C651,935.2 658.2,942.4 667.4,942.4Z" /> + android:pathData="M860.9,940.9C869.1,940.9 875.7,934.2 875.7,926C875.7,917.8 869.1,911.1 860.9,911.1C852.7,911.1 846,917.8 846,926C846,934.2 852.7,940.9 860.9,940.9Z" /> + android:pathData="M909.1,946.5C920.4,946.5 929.6,937.3 929.6,926C929.6,914.7 920.4,905.5 909.1,905.5C897.8,905.5 888.6,914.7 888.6,926C888.6,937.3 898.3,946.5 909.1,946.5Z" /> + android:pathData="M957.8,940.9C966,940.9 972.7,934.2 972.7,926C972.7,917.8 966,911.1 957.8,911.1C949.6,911.1 943,917.8 943,926C943,934.2 949.6,940.9 957.8,940.9Z" /> + android:pathData="M1006.1,946.5C1017.3,946.5 1026.6,937.3 1026.6,926C1026.6,914.7 1017.3,905.5 1006.1,905.5C994.8,905.5 985.5,914.7 985.5,926C985.5,937.3 994.8,946.5 1006.1,946.5Z" /> + android:pathData="M1054.3,942.9C1063.5,942.9 1071.2,935.2 1071.2,926C1071.2,916.8 1063.5,909.1 1054.3,909.1C1045.1,909.1 1037.4,916.8 1037.4,926C1037.9,935.2 1045.1,942.9 1054.3,942.9Z" /> + android:pathData="M1103,942.9C1112.3,942.9 1120,935.2 1120,926C1120,916.8 1112.3,909.1 1103,909.1C1093.8,909.1 1086.1,916.8 1086.1,926C1086.1,935.2 1093.3,942.9 1103,942.9Z" /> + android:pathData="M1151.3,942.9C1160.5,942.9 1168.2,935.2 1168.2,926C1168.2,916.8 1160.5,909.1 1151.3,909.1C1142,909.1 1134.3,916.8 1134.3,926C1134.3,935.2 1142,942.9 1151.3,942.9Z" /> + android:pathData="M1200,942.9C1209.2,942.9 1216.9,935.2 1216.9,926C1216.9,916.3 1209.2,908.6 1200,908.6C1190.2,908.6 1182.6,916.3 1182.6,926C1182.6,935.2 1190.2,942.9 1200,942.9Z" /> + android:pathData="M1248.2,940.9C1256.4,940.9 1263.1,934.2 1263.1,926C1263.1,917.8 1256.4,911.1 1248.2,911.1C1240,911.1 1233.3,917.8 1233.3,926C1233.3,934.2 1240,940.9 1248.2,940.9Z" /> + android:pathData="M1296.5,944C1306.7,944 1314.4,935.7 1314.4,926C1314.4,915.7 1306.2,908 1296.5,908C1286.7,908 1278.5,916.3 1278.5,926C1278.5,935.7 1286.7,944 1296.5,944Z" /> + android:pathData="M1345.2,943.4C1354.9,943.4 1362.6,935.7 1362.6,926C1362.6,916.3 1354.9,908.6 1345.2,908.6C1335.4,908.6 1327.7,916.3 1327.7,926C1327.7,935.7 1335.4,943.4 1345.2,943.4Z" /> + android:pathData="M570.5,989.1C578.7,989.1 585.4,982.4 585.4,974.2C585.4,966 578.7,959.4 570.5,959.4C562.3,959.4 555.6,966 555.6,974.2C555.6,982.4 562.3,989.1 570.5,989.1Z" /> + android:pathData="M619.2,989.1C627.4,989.1 634.1,982.4 634.1,974.2C634.1,966 627.4,959.4 619.2,959.4C611,959.4 604.3,966 604.3,974.2C604.3,982.4 611,989.1 619.2,989.1Z" /> + android:pathData="M667.4,989.1C675.7,989.1 682.3,982.4 682.3,974.2C682.3,966 675.7,959.4 667.4,959.4C659.2,959.4 652.6,966 652.6,974.2C652.6,982.4 659.2,989.1 667.4,989.1Z" /> + android:pathData="M715.7,989.1C723.9,989.1 730.6,982.4 730.6,974.2C730.6,966 723.9,959.4 715.7,959.4C707.5,959.4 700.8,966 700.8,974.2C700.8,982.4 707.5,989.1 715.7,989.1Z" /> + android:pathData="M860.9,989.1C869.1,989.1 875.7,982.4 875.7,974.2C875.7,966 869.1,959.4 860.9,959.4C852.7,959.4 846,966 846,974.2C846,982.4 852.7,989.1 860.9,989.1Z" /> + android:pathData="M909.1,989.1C917.3,989.1 924,982.4 924,974.2C924,966 917.3,959.4 909.1,959.4C900.9,959.4 894.2,966 894.2,974.2C894.7,982.4 901.4,989.1 909.1,989.1Z" /> + android:pathData="M957.8,993.2C968.1,993.2 976.8,984.5 976.8,974.2C976.8,963.5 968.1,955.2 957.8,955.2C947.1,955.2 938.9,964 938.9,974.2C938.9,985 947.1,993.2 957.8,993.2Z" /> + android:pathData="M1006.1,994.8C1017.3,994.8 1026.6,985.5 1026.6,974.2C1026.6,962.9 1017.3,953.7 1006.1,953.7C994.8,953.7 985.5,962.9 985.5,974.2C985.5,985.5 994.8,994.8 1006.1,994.8Z" /> + android:pathData="M1054.3,994.8C1065.6,994.8 1074.8,985.5 1074.8,974.2C1074.8,962.9 1065.6,953.7 1054.3,953.7C1043,953.7 1033.8,962.9 1033.8,974.2C1033.8,985.5 1043,994.8 1054.3,994.8Z" /> + android:pathData="M1103,991.7C1112.3,991.7 1120,984 1120,974.7C1120,965.5 1112.3,957.8 1103,957.8C1093.8,957.8 1086.1,965.5 1086.1,974.7C1086.1,984 1093.3,991.7 1103,991.7Z" /> + android:pathData="M1151.3,989.1C1159.5,989.1 1166.1,982.4 1166.1,974.2C1166.1,966 1159.5,959.4 1151.3,959.4C1143,959.4 1136.4,966 1136.4,974.2C1136.4,982.4 1143,989.1 1151.3,989.1Z" /> + android:pathData="M1200,989.1C1208.2,989.1 1214.9,982.4 1214.9,974.2C1214.9,966 1208.2,959.4 1200,959.4C1191.8,959.4 1185.1,966 1185.1,974.2C1185.1,982.4 1191.8,989.1 1200,989.1Z" /> + android:pathData="M1248.2,989.1C1256.4,989.1 1263.1,982.4 1263.1,974.2C1263.1,966 1256.4,959.4 1248.2,959.4C1240,959.4 1233.3,966 1233.3,974.2C1233.3,982.4 1240,989.1 1248.2,989.1Z" /> + android:pathData="M1296.5,992.7C1306.7,992.7 1314.4,984.5 1314.4,974.2C1314.4,964.5 1306.2,956.3 1296.5,956.3C1286.7,956.3 1278.5,964.5 1278.5,974.2C1278.5,984.5 1286.7,992.7 1296.5,992.7Z" /> + android:pathData="M1345.2,991.7C1354.9,991.7 1362.6,984 1362.6,974.2C1362.6,964.5 1354.9,956.8 1345.2,956.8C1335.4,956.8 1327.7,964.5 1327.7,974.2C1327.2,984 1335.4,991.7 1345.2,991.7Z" /> + android:pathData="M570.5,1037.3C578.7,1037.3 585.4,1030.7 585.4,1022.5C585.4,1014.2 578.7,1007.6 570.5,1007.6C562.3,1007.6 555.6,1014.2 555.6,1022.5C555.6,1031.2 562.3,1037.3 570.5,1037.3Z" /> + android:pathData="M619.2,1038.9C627.9,1038.9 635.6,1031.7 635.6,1022.5C635.6,1013.2 627.9,1006 619.2,1006C610,1006 602.8,1013.2 602.8,1022.5C602.8,1031.7 610,1038.9 619.2,1038.9Z" /> + android:pathData="M667.4,1037.3C675.7,1037.3 682.3,1030.7 682.3,1022.5C682.3,1014.2 675.7,1007.6 667.4,1007.6C659.2,1007.6 652.6,1014.2 652.6,1022.5C652.6,1031.2 659.2,1037.3 667.4,1037.3Z" /> + android:pathData="M715.7,1037.3C723.9,1037.3 730.6,1030.7 730.6,1022.5C730.6,1014.2 723.9,1007.6 715.7,1007.6C707.5,1007.6 700.8,1014.2 700.8,1022.5C700.8,1031.2 707.5,1037.3 715.7,1037.3Z" /> + android:pathData="M764.4,1042C774.7,1042 783.4,1033.2 783.4,1023C783.4,1012.2 774.7,1004 764.4,1004C753.6,1004 745.4,1012.7 745.4,1023C744.9,1033.2 753.6,1042 764.4,1042Z" /> + android:pathData="M812.6,1037.8C820.8,1037.8 828,1031.2 828,1022.5C828,1014.2 821.4,1007.1 812.6,1007.1C804.4,1007.1 797.2,1013.7 797.2,1022.5C797.2,1031.2 804.4,1037.8 812.6,1037.8Z" /> + android:pathData="M860.9,1037.3C869.1,1037.3 875.7,1030.7 875.7,1022.5C875.7,1014.2 869.1,1007.6 860.9,1007.6C852.7,1007.6 846,1014.2 846,1022.5C846,1031.2 852.7,1037.3 860.9,1037.3Z" /> + android:pathData="M909.1,1037.3C917.3,1037.3 924,1030.7 924,1022.5C924,1014.2 917.3,1007.6 909.1,1007.6C900.9,1007.6 894.2,1014.2 894.2,1022.5C894.7,1031.2 901.4,1037.3 909.1,1037.3Z" /> + android:pathData="M957.8,1042C968.1,1042 976.8,1033.2 976.8,1023C976.8,1012.2 968.1,1004 957.8,1004C947.1,1004 938.9,1012.7 938.9,1023C938.9,1033.2 947.1,1042 957.8,1042Z" /> + android:pathData="M1006.1,1039.9C1015.3,1039.9 1023,1032.2 1023,1022.5C1023,1012.7 1015.3,1005 1006.1,1005C996.3,1005 988.6,1012.7 988.6,1022.5C989.1,1032.2 996.8,1039.9 1006.1,1039.9Z" /> + android:pathData="M1054.3,1039.4C1063.5,1039.4 1071.2,1031.7 1071.2,1022.5C1071.2,1013.2 1063.5,1005.5 1054.3,1005.5C1045.1,1005.5 1037.4,1013.2 1037.4,1022.5C1037.9,1032.2 1045.1,1039.4 1054.3,1039.4Z" /> + android:pathData="M1103,1039.9C1112.3,1039.9 1120,1032.2 1120,1023C1120,1013.7 1112.3,1006 1103,1006C1093.8,1006 1086.1,1013.7 1086.1,1023C1086.1,1032.2 1093.3,1039.9 1103,1039.9Z" /> + android:pathData="M1151.3,1039.9C1160.5,1039.9 1168.2,1032.2 1168.2,1023C1168.2,1013.7 1160.5,1006 1151.3,1006C1142,1006 1134.3,1013.7 1134.3,1023C1134.3,1032.2 1142,1039.9 1151.3,1039.9Z" /> + android:pathData="M1200,1037.3C1208.2,1037.3 1214.9,1030.7 1214.9,1022.5C1214.9,1014.2 1208.2,1007.6 1200,1007.6C1191.8,1007.6 1185.1,1014.2 1185.1,1022.5C1185.1,1031.2 1191.8,1037.3 1200,1037.3Z" /> + android:pathData="M1248.2,1039.9C1258,1039.9 1265.7,1032.2 1265.7,1022.5C1265.7,1012.7 1258,1005 1248.2,1005C1239,1005 1231.3,1012.7 1231.3,1022.5C1230.8,1032.2 1238.5,1039.9 1248.2,1039.9Z" /> + android:pathData="M1296.5,1040.4C1306.2,1040.4 1313.9,1032.7 1313.9,1023C1313.9,1013.2 1306.2,1005.5 1296.5,1005.5C1286.7,1005.5 1279,1013.2 1279,1023C1279,1032.2 1286.7,1040.4 1296.5,1040.4Z" /> + android:pathData="M1345.2,1040.4C1354.9,1040.4 1362.6,1032.7 1362.6,1023C1362.6,1013.2 1354.9,1005.5 1345.2,1005.5C1335.4,1005.5 1327.7,1013.2 1327.7,1023C1327.2,1032.2 1335.4,1040.4 1345.2,1040.4Z" /> + android:pathData="M619.2,1086.1C627.4,1086.1 634.1,1079.4 634.1,1071.2C634.1,1063 627.4,1056.3 619.2,1056.3C611,1056.3 604.3,1063 604.3,1071.2C604.3,1079.4 611,1086.1 619.2,1086.1Z" /> + android:pathData="M667.4,1086.1C675.7,1086.1 682.3,1079.4 682.3,1071.2C682.3,1063 675.7,1056.3 667.4,1056.3C659.2,1056.3 652.6,1063 652.6,1071.2C652.6,1079.4 659.2,1086.1 667.4,1086.1Z" /> + android:pathData="M764.4,1086.1C772.6,1086.1 779.3,1079.4 779.3,1071.2C779.3,1063 772.6,1056.3 764.4,1056.3C756.2,1056.3 749.5,1063 749.5,1071.2C749.5,1079.4 755.7,1086.1 764.4,1086.1Z" /> + android:pathData="M812.6,1086.1C820.8,1086.1 827.5,1079.4 827.5,1071.2C827.5,1063 820.8,1056.3 812.6,1056.3C804.4,1056.3 797.8,1063 797.8,1071.2C797.8,1079.4 804.4,1086.1 812.6,1086.1Z" /> + android:pathData="M860.9,1086.1C869.1,1086.1 875.7,1079.4 875.7,1071.2C875.7,1063 869.1,1056.3 860.9,1056.3C852.7,1056.3 846,1063 846,1071.2C846,1079.4 852.7,1086.1 860.9,1086.1Z" /> + android:pathData="M909.1,1086.1C917.3,1086.1 924,1079.4 924,1071.2C924,1063 917.3,1056.3 909.1,1056.3C900.9,1056.3 894.2,1063 894.2,1071.2C894.7,1079.4 901.4,1086.1 909.1,1086.1Z" /> + android:pathData="M957.8,1086.1C966,1086.1 972.7,1079.4 972.7,1071.2C972.7,1063 966,1056.3 957.8,1056.3C949.6,1056.3 943,1063 943,1071.2C943,1079.4 949.6,1086.1 957.8,1086.1Z" /> + android:pathData="M1006.1,1088.1C1015.3,1088.1 1023,1080.4 1023,1070.7C1023,1061.4 1015.3,1053.8 1006.1,1053.8C996.3,1053.8 988.6,1061.4 988.6,1070.7C989.1,1080.4 996.8,1088.1 1006.1,1088.1Z" /> + android:pathData="M1054.3,1088.1C1063.5,1088.1 1071.2,1080.4 1071.2,1071.2C1071.2,1062 1063.5,1054.3 1054.3,1054.3C1045.1,1054.3 1037.4,1062 1037.4,1071.2C1037.9,1080.4 1045.1,1088.1 1054.3,1088.1Z" /> + android:pathData="M1103,1088.1C1112.3,1088.1 1120,1080.4 1120,1071.2C1120,1062 1112.3,1054.3 1103,1054.3C1093.8,1054.3 1086.1,1062 1086.1,1071.2C1086.1,1080.4 1093.3,1088.1 1103,1088.1Z" /> + android:pathData="M1151.3,1088.1C1160.5,1088.1 1168.2,1080.4 1168.2,1071.2C1168.2,1062 1160.5,1054.3 1151.3,1054.3C1142,1054.3 1134.3,1062 1134.3,1071.2C1134.3,1080.4 1142,1088.1 1151.3,1088.1Z" /> + android:pathData="M1200,1088.1C1209.2,1088.1 1216.9,1080.4 1216.9,1070.7C1216.9,1061.4 1209.2,1053.8 1200,1053.8C1190.2,1053.8 1182.6,1061.4 1182.6,1070.7C1182.6,1080.4 1190.2,1088.1 1200,1088.1Z" /> + android:pathData="M1248.2,1088.1C1258,1088.1 1265.7,1080.4 1265.7,1070.7C1265.7,1061.4 1258,1053.8 1248.2,1053.8C1239,1053.8 1231.3,1061.4 1231.3,1070.7C1230.8,1080.4 1238.5,1088.1 1248.2,1088.1Z" /> + android:pathData="M1296.5,1088.6C1306.2,1088.6 1313.9,1080.9 1313.9,1071.2C1313.9,1061.4 1306.2,1053.8 1296.5,1053.8C1286.7,1053.8 1279,1061.4 1279,1071.2C1279,1080.9 1286.7,1088.6 1296.5,1088.6Z" /> + android:pathData="M1345.2,1088.6C1354.9,1088.6 1362.6,1080.9 1362.6,1071.2C1362.6,1061.4 1354.9,1053.8 1345.2,1053.8C1335.4,1053.8 1327.7,1061.4 1327.7,1071.2C1327.7,1080.9 1335.4,1088.6 1345.2,1088.6Z" /> + android:pathData="M377.1,1134.3C385.3,1134.3 391.9,1127.6 391.9,1119.4C391.9,1111.2 385.3,1104.5 377.1,1104.5C368.8,1104.5 362.2,1111.2 362.2,1119.4C362.2,1127.6 368.8,1134.3 377.1,1134.3Z" /> + android:pathData="M425.3,1134.3C433.5,1134.3 440.2,1127.6 440.2,1119.4C440.2,1111.2 433.5,1104.5 425.3,1104.5C417.1,1104.5 410.4,1111.2 410.4,1119.4C410.4,1127.6 417.1,1134.3 425.3,1134.3Z" /> + android:pathData="M473.5,1134.3C481.7,1134.3 488.4,1127.6 488.4,1119.4C488.4,1111.2 481.7,1104.5 473.5,1104.5C465.3,1104.5 458.6,1111.2 458.6,1119.4C459.1,1127.6 465.8,1134.3 473.5,1134.3Z" /> + android:pathData="M619.2,1134.3C627.4,1134.3 634.1,1127.6 634.1,1119.4C634.1,1111.2 627.4,1104.5 619.2,1104.5C611,1104.5 604.3,1111.2 604.3,1119.4C604.3,1127.6 611,1134.3 619.2,1134.3Z" /> + android:pathData="M667.4,1134.8C675.7,1134.8 682.8,1128.1 682.8,1119.9C682.8,1111.7 676.2,1105.1 667.4,1105.1C659.2,1105.1 652.6,1111.7 652.6,1119.9C652.6,1127.6 659.2,1134.8 667.4,1134.8Z" /> + android:pathData="M715.7,1134.8C724.4,1134.8 731.1,1127.6 731.1,1119.4C731.1,1110.7 723.9,1104 715.7,1104C707,1104 700.3,1110.7 700.3,1119.4C700.3,1128.1 707.5,1134.8 715.7,1134.8Z" /> + android:pathData="M764.4,1136.4C773.6,1136.4 780.8,1128.7 780.8,1119.9C780.8,1110.7 773.1,1103.5 764.4,1103.5C755.7,1103.5 748,1110.7 748,1119.9C747.5,1128.7 755.2,1136.4 764.4,1136.4Z" /> + android:pathData="M812.6,1134.3C820.8,1134.3 827.5,1127.6 827.5,1119.4C827.5,1111.2 820.8,1104.5 812.6,1104.5C804.4,1104.5 797.8,1111.2 797.8,1119.4C797.8,1127.6 804.4,1134.3 812.6,1134.3Z" /> + android:pathData="M860.9,1134.3C869.1,1134.3 875.7,1127.6 875.7,1119.4C875.7,1111.2 869.1,1104.5 860.9,1104.5C852.7,1104.5 846,1111.2 846,1119.4C846,1127.6 852.7,1134.3 860.9,1134.3Z" /> + android:pathData="M909.1,1139.9C920.4,1139.9 929.1,1130.7 929.1,1119.9C929.1,1108.6 920.4,1099.9 909.1,1099.9C898.3,1099.9 889.1,1109.2 889.1,1119.9C889.1,1130.7 898.3,1139.9 909.1,1139.9Z" /> + android:pathData="M957.8,1136.4C967.1,1136.4 974.3,1128.7 974.3,1119.9C974.3,1110.7 966.6,1103.5 957.8,1103.5C949.1,1103.5 941.4,1110.7 941.4,1119.9C941.4,1128.7 948.6,1136.4 957.8,1136.4Z" /> + android:pathData="M1006.1,1136.4C1015.3,1136.4 1022.5,1128.7 1022.5,1119.9C1022.5,1110.7 1014.8,1103.5 1006.1,1103.5C996.8,1103.5 989.6,1110.7 989.6,1119.9C989.6,1128.7 996.8,1136.4 1006.1,1136.4Z" /> + android:pathData="M1054.3,1136.4C1063.5,1136.4 1071.2,1128.7 1071.2,1119.4C1071.2,1110.2 1063.5,1102.5 1054.3,1102.5C1045.1,1102.5 1037.4,1110.2 1037.4,1119.4C1037.9,1128.7 1045.1,1136.4 1054.3,1136.4Z" /> + android:pathData="M1103,1136.4C1112.3,1136.4 1120,1128.7 1120,1119.4C1120,1110.2 1112.3,1102.5 1103,1102.5C1093.8,1102.5 1086.1,1110.2 1086.1,1119.4C1086.1,1129.2 1093.3,1136.4 1103,1136.4Z" /> + android:pathData="M1151.3,1136.4C1160.5,1136.4 1168.2,1128.7 1168.2,1119.4C1168.2,1110.2 1160.5,1102.5 1151.3,1102.5C1142,1102.5 1134.3,1110.2 1134.3,1119.4C1134.3,1129.2 1142,1136.4 1151.3,1136.4Z" /> + android:pathData="M1200,1136.9C1209.2,1136.9 1216.9,1129.2 1216.9,1119.4C1216.9,1110.2 1209.2,1102 1200,1102C1190.2,1102 1182.6,1109.7 1182.6,1119.4C1182.6,1129.2 1190.2,1136.9 1200,1136.9Z" /> + android:pathData="M1248.2,1136.9C1258,1136.9 1265.7,1129.2 1265.7,1119.4C1265.7,1110.2 1258,1102 1248.2,1102C1239,1102 1231.3,1109.7 1231.3,1119.4C1230.8,1129.2 1238.5,1136.9 1248.2,1136.9Z" /> + android:pathData="M1296.5,1136.9C1306.2,1136.9 1313.9,1129.2 1313.9,1119.4C1313.9,1109.7 1306.2,1102 1296.5,1102C1286.7,1102 1279,1109.7 1279,1119.4C1279,1129.2 1286.7,1136.9 1296.5,1136.9Z" /> + android:pathData="M1345.2,1136.9C1354.9,1136.9 1362.6,1129.2 1362.6,1119.4C1362.6,1109.7 1354.9,1102 1345.2,1102C1335.4,1102 1327.7,1109.7 1327.7,1119.4C1327.2,1129.2 1335.4,1136.9 1345.2,1136.9Z" /> + android:pathData="M183.1,1183C191.3,1183 198,1176.4 198,1168.2C198,1160 191.3,1153.3 183.1,1153.3C174.9,1153.3 168.2,1160 168.2,1168.2C168.8,1175.9 175.4,1183 183.1,1183Z" /> + android:pathData="M231.9,1188.7C243.2,1188.7 252.4,1179.5 252.4,1168.2C252.4,1156.9 243.2,1147.6 231.9,1147.6C220.6,1147.6 211.3,1156.9 211.3,1168.2C211.3,1179.5 220.6,1188.7 231.9,1188.7Z" /> + android:pathData="M570.5,1183C578.7,1183 585.4,1176.4 585.4,1168.2C585.4,1160 578.7,1153.3 570.5,1153.3C562.3,1153.3 555.6,1160 555.6,1168.2C555.6,1175.9 562.3,1183 570.5,1183Z" /> + android:pathData="M619.2,1183C627.4,1183 634.1,1176.4 634.1,1168.2C634.1,1160 627.4,1153.3 619.2,1153.3C611,1153.3 604.3,1160 604.3,1168.2C604.3,1175.9 611,1183 619.2,1183Z" /> + android:pathData="M715.7,1183C723.9,1183 730.6,1176.4 730.6,1168.2C730.6,1160 723.9,1153.3 715.7,1153.3C707.5,1153.3 700.8,1160 700.8,1168.2C700.8,1175.9 707.5,1183 715.7,1183Z" /> + android:pathData="M764.4,1183C772.6,1183 779.3,1176.4 779.3,1168.2C779.3,1160 772.6,1153.3 764.4,1153.3C756.2,1153.3 749.5,1160 749.5,1168.2C749.5,1175.9 755.7,1183 764.4,1183Z" /> + android:pathData="M812.6,1183.6C821.4,1183.6 828.5,1176.4 828.5,1167.7C828.5,1158.9 821.4,1151.7 812.6,1151.7C803.9,1151.7 796.7,1158.9 796.7,1167.7C796.7,1176.9 803.9,1183.6 812.6,1183.6Z" /> + android:pathData="M860.9,1184.6C870.1,1184.6 877.3,1177.4 877.3,1168.2C877.3,1158.9 870.1,1151.7 860.9,1151.7C851.6,1151.7 844.5,1158.9 844.5,1168.2C844.5,1176.9 851.6,1184.6 860.9,1184.6Z" /> + android:pathData="M909.1,1184.6C918.3,1184.6 925.5,1177.4 925.5,1168.2C925.5,1158.9 917.8,1151.7 909.1,1151.7C899.9,1151.7 892.7,1158.9 892.7,1168.2C892.7,1176.9 900.4,1184.6 909.1,1184.6Z" /> + android:pathData="M957.8,1183C966,1183 972.7,1176.4 972.7,1168.2C972.7,1160 966,1153.3 957.8,1153.3C949.6,1153.3 943,1160 943,1168.2C943,1175.9 949.6,1183 957.8,1183Z" /> + android:pathData="M1006.1,1184.6C1015.3,1184.6 1022.5,1177.4 1022.5,1168.2C1022.5,1158.9 1014.8,1151.7 1006.1,1151.7C996.8,1151.7 989.6,1158.9 989.6,1168.2C989.6,1176.9 996.8,1184.6 1006.1,1184.6Z" /> + android:pathData="M1054.3,1184.6C1063.5,1184.6 1071.2,1176.9 1071.2,1167.7C1071.2,1158.4 1063.5,1150.7 1054.3,1150.7C1045.1,1150.7 1037.4,1158.4 1037.4,1167.7C1037.9,1177.4 1045.1,1184.6 1054.3,1184.6Z" /> + android:pathData="M1103,1185.1C1112.3,1185.1 1120,1177.4 1120,1168.2C1120,1158.9 1112.3,1151.2 1103,1151.2C1093.8,1151.2 1086.1,1158.9 1086.1,1168.2C1086.1,1177.4 1093.3,1185.1 1103,1185.1Z" /> + android:pathData="M1151.3,1185.1C1160.5,1185.1 1168.2,1177.4 1168.2,1168.2C1168.2,1158.9 1160.5,1151.2 1151.3,1151.2C1142,1151.2 1134.3,1158.9 1134.3,1168.2C1134.3,1177.4 1142,1185.1 1151.3,1185.1Z" /> + android:pathData="M1200,1185.1C1209.2,1185.1 1216.9,1177.4 1216.9,1167.7C1216.9,1158.4 1209.2,1150.7 1200,1150.7C1190.2,1150.7 1182.6,1158.4 1182.6,1167.7C1182.6,1177.4 1190.2,1185.1 1200,1185.1Z" /> + android:pathData="M1248.2,1185.1C1258,1185.1 1265.7,1177.4 1265.7,1167.7C1265.7,1158.4 1258,1150.7 1248.2,1150.7C1239,1150.7 1231.3,1158.4 1231.3,1167.7C1230.8,1177.4 1238.5,1185.1 1248.2,1185.1Z" /> + android:pathData="M1296.5,1185.6C1306.2,1185.6 1313.9,1177.9 1313.9,1168.2C1313.9,1158.4 1306.2,1150.7 1296.5,1150.7C1286.7,1150.7 1279,1158.4 1279,1168.2C1279,1177.4 1286.7,1185.6 1296.5,1185.6Z" /> + android:pathData="M1345.2,1185.6C1354.9,1185.6 1362.6,1177.9 1362.6,1168.2C1362.6,1158.4 1354.9,1150.7 1345.2,1150.7C1335.4,1150.7 1327.7,1158.4 1327.7,1168.2C1327.2,1177.4 1335.4,1185.6 1345.2,1185.6Z" /> + android:pathData="M764.4,1231.3C772.6,1231.3 779.3,1224.6 779.3,1216.4C779.3,1208.2 772.6,1201.5 764.4,1201.5C756.2,1201.5 749.5,1208.2 749.5,1216.4C749.5,1224.6 755.7,1231.3 764.4,1231.3Z" /> + android:pathData="M860.9,1231.3C869.1,1231.3 875.7,1224.6 875.7,1216.4C875.7,1208.2 869.1,1201.5 860.9,1201.5C852.7,1201.5 846,1208.2 846,1216.4C846,1224.6 852.7,1231.3 860.9,1231.3Z" /> + android:pathData="M909.1,1231.3C917.3,1231.3 924,1224.6 924,1216.4C924,1208.2 917.3,1201.5 909.1,1201.5C900.9,1201.5 894.2,1208.2 894.2,1216.4C894.2,1224.6 901.4,1231.3 909.1,1231.3Z" /> + android:pathData="M957.8,1231.3C966,1231.3 972.7,1224.6 972.7,1216.4C972.7,1208.2 966,1201.5 957.8,1201.5C949.6,1201.5 943,1208.2 943,1216.4C943,1224.6 949.6,1231.3 957.8,1231.3Z" /> + android:pathData="M1006.1,1231.3C1014.3,1231.3 1020.9,1224.6 1020.9,1216.4C1020.9,1208.2 1014.3,1201.5 1006.1,1201.5C997.9,1201.5 991.2,1208.2 991.2,1216.4C991.2,1224.6 997.9,1231.3 1006.1,1231.3Z" /> + android:pathData="M1054.3,1235.9C1065.1,1235.9 1073.8,1227.2 1073.8,1216.4C1073.8,1205.6 1065.1,1196.9 1054.3,1196.9C1043.5,1196.9 1034.8,1205.6 1034.8,1216.4C1035.3,1227.2 1044,1235.9 1054.3,1235.9Z" /> + android:pathData="M1103,1231.3C1111.2,1231.3 1117.9,1224.6 1117.9,1216.4C1117.9,1208.2 1111.2,1201.5 1103,1201.5C1094.8,1201.5 1088.2,1208.2 1088.2,1216.4C1088.2,1224.6 1094.8,1231.3 1103,1231.3Z" /> + android:pathData="M1151.3,1231.3C1159.5,1231.3 1166.1,1224.6 1166.1,1216.4C1166.1,1208.2 1159.5,1201.5 1151.3,1201.5C1143,1201.5 1136.4,1208.2 1136.4,1216.4C1136.4,1224.6 1143,1231.3 1151.3,1231.3Z" /> + android:pathData="M1200,1231.3C1208.2,1231.3 1214.9,1224.6 1214.9,1216.4C1214.9,1208.2 1208.2,1201.5 1200,1201.5C1191.8,1201.5 1185.1,1208.2 1185.1,1216.4C1185.1,1224.6 1191.8,1231.3 1200,1231.3Z" /> + android:pathData="M1248.2,1233.8C1258,1233.8 1265.7,1226.1 1265.7,1216.4C1265.7,1207.2 1258,1198.9 1248.2,1198.9C1239,1198.9 1231.3,1206.6 1231.3,1216.4C1230.8,1225.6 1238.5,1233.8 1248.2,1233.8Z" /> + android:pathData="M1296.5,1233.8C1306.2,1233.8 1313.9,1226.1 1313.9,1216.4C1313.9,1206.6 1306.2,1198.9 1296.5,1198.9C1286.7,1198.9 1279,1206.6 1279,1216.4C1279,1226.1 1286.7,1233.8 1296.5,1233.8Z" /> + android:pathData="M1345.2,1233.8C1354.9,1233.8 1362.6,1226.1 1362.6,1216.4C1362.6,1206.6 1354.9,1198.9 1345.2,1198.9C1335.4,1198.9 1327.7,1206.6 1327.7,1216.4C1327.7,1226.1 1335.4,1233.8 1345.2,1233.8Z" /> + android:pathData="M667.4,1279.5C675.7,1279.5 682.3,1272.8 682.3,1264.6C682.3,1256.4 675.7,1249.7 667.4,1249.7C659.2,1249.7 652.6,1256.4 652.6,1264.6C652.6,1272.8 659.2,1279.5 667.4,1279.5Z" /> + android:pathData="M715.7,1282.6C725.4,1282.6 733.6,1274.4 733.6,1264.6C733.6,1254.9 725.9,1246.7 715.7,1246.7C705.9,1246.7 697.7,1254.9 697.7,1264.6C698.2,1274.4 705.9,1282.6 715.7,1282.6Z" /> + android:pathData="M764.4,1279.5C772.6,1279.5 779.3,1272.8 779.3,1264.6C779.3,1256.4 772.6,1249.7 764.4,1249.7C756.2,1249.7 749.5,1256.4 749.5,1264.6C749.5,1272.8 755.7,1279.5 764.4,1279.5Z" /> + android:pathData="M812.6,1279.5C820.8,1279.5 827.5,1272.8 827.5,1264.6C827.5,1256.4 820.8,1249.7 812.6,1249.7C804.4,1249.7 797.8,1256.4 797.8,1264.6C797.8,1272.8 804.4,1279.5 812.6,1279.5Z" /> + android:pathData="M860.9,1279.5C869.1,1279.5 875.7,1272.8 875.7,1264.6C875.7,1256.4 869.1,1249.7 860.9,1249.7C852.7,1249.7 846,1256.4 846,1264.6C846,1272.8 852.7,1279.5 860.9,1279.5Z" /> + android:pathData="M909.1,1279.5C917.3,1279.5 924,1272.8 924,1264.6C924,1256.4 917.3,1249.7 909.1,1249.7C900.9,1249.7 894.2,1256.4 894.2,1264.6C894.7,1272.8 901.4,1279.5 909.1,1279.5Z" /> + android:pathData="M957.8,1280.5C966.6,1280.5 973.7,1273.3 973.7,1264.6C973.7,1255.9 966.6,1248.7 957.8,1248.7C949.1,1248.7 941.9,1255.9 941.9,1264.6C941.9,1273.3 949.1,1280.5 957.8,1280.5Z" /> + android:pathData="M1006.1,1279.5C1014.3,1279.5 1020.9,1272.8 1020.9,1264.6C1020.9,1256.4 1014.3,1249.7 1006.1,1249.7C997.9,1249.7 991.2,1256.4 991.2,1264.6C991.7,1272.8 997.9,1279.5 1006.1,1279.5Z" /> + android:pathData="M1054.3,1279.5C1062.5,1279.5 1069.2,1272.8 1069.2,1264.6C1069.2,1256.4 1062.5,1249.7 1054.3,1249.7C1046.1,1249.7 1039.4,1256.4 1039.4,1264.6C1039.9,1272.8 1046.6,1279.5 1054.3,1279.5Z" /> + android:pathData="M1103,1279.5C1111.2,1279.5 1117.9,1272.8 1117.9,1264.6C1117.9,1256.4 1111.2,1249.7 1103,1249.7C1094.8,1249.7 1088.2,1256.4 1088.2,1264.6C1088.2,1272.8 1094.8,1279.5 1103,1279.5Z" /> + android:pathData="M1151.3,1279.5C1159.5,1279.5 1166.1,1272.8 1166.1,1264.6C1166.1,1256.4 1159.5,1249.7 1151.3,1249.7C1143,1249.7 1136.4,1256.4 1136.4,1264.6C1136.4,1272.8 1143,1279.5 1151.3,1279.5Z" /> + android:pathData="M1200,1285.1C1211.3,1285.1 1220.5,1275.9 1220.5,1264.6C1220.5,1253.3 1211.3,1244.1 1200,1244.1C1188.7,1244.1 1179.5,1253.3 1179.5,1264.6C1179,1275.9 1188.2,1285.1 1200,1285.1Z" /> + android:pathData="M1248.2,1282.1C1258,1282.1 1265.7,1274.4 1265.7,1264.6C1265.7,1255.4 1258,1247.7 1248.2,1247.7C1239,1247.7 1231.3,1255.4 1231.3,1264.6C1230.8,1274.4 1238.5,1282.1 1248.2,1282.1Z" /> + android:pathData="M1296.5,1282.1C1306.2,1282.1 1313.9,1274.4 1313.9,1264.6C1313.9,1254.9 1306.2,1247.2 1296.5,1247.2C1286.7,1247.2 1279,1254.9 1279,1264.6C1279,1274.4 1286.7,1282.1 1296.5,1282.1Z" /> + android:pathData="M1345.2,1282.1C1354.9,1282.1 1362.6,1274.4 1362.6,1264.6C1362.6,1254.9 1354.9,1247.2 1345.2,1247.2C1335.4,1247.2 1327.7,1254.9 1327.7,1264.6C1327.2,1274.4 1335.4,1282.1 1345.2,1282.1Z" /> + android:pathData="M667.4,1327.7C675.7,1327.7 682.3,1321.1 682.3,1312.8C682.3,1304.6 675.7,1298 667.4,1298C659.2,1298 652.6,1304.6 652.6,1312.8C652.6,1321.6 659.2,1327.7 667.4,1327.7Z" /> + android:pathData="M715.7,1328.2C723.9,1328.2 731.1,1321.6 731.1,1313.4C731.1,1305.1 724.4,1298.5 715.7,1298.5C707.5,1298.5 700.8,1305.1 700.8,1313.4C700.8,1321.6 707.5,1328.2 715.7,1328.2Z" /> + android:pathData="M764.4,1327.7C772.6,1327.7 779.3,1321.1 779.3,1312.8C779.3,1304.6 772.6,1298 764.4,1298C756.2,1298 749.5,1304.6 749.5,1312.8C749.5,1321.6 755.7,1327.7 764.4,1327.7Z" /> + android:pathData="M812.6,1327.7C820.8,1327.7 827.5,1321.1 827.5,1312.8C827.5,1304.6 820.8,1298 812.6,1298C804.4,1298 797.8,1304.6 797.8,1312.8C797.8,1321.6 804.4,1327.7 812.6,1327.7Z" /> + android:pathData="M860.9,1327.7C869.1,1327.7 875.7,1321.1 875.7,1312.8C875.7,1304.6 869.1,1298 860.9,1298C852.7,1298 846,1304.6 846,1312.8C846,1321.6 852.7,1327.7 860.9,1327.7Z" /> + android:pathData="M909.1,1327.7C917.3,1327.7 924,1321.1 924,1312.8C924,1304.6 917.3,1298 909.1,1298C900.9,1298 894.2,1304.6 894.2,1312.8C894.7,1321.6 901.4,1327.7 909.1,1327.7Z" /> + android:pathData="M957.8,1327.7C966,1327.7 972.7,1321.1 972.7,1312.8C972.7,1304.6 966,1298 957.8,1298C949.6,1298 943,1304.6 943,1312.8C943,1321.6 949.6,1327.7 957.8,1327.7Z" /> + android:pathData="M1006.1,1329.8C1015.3,1329.8 1022.5,1322.6 1022.5,1313.4C1022.5,1304.1 1014.8,1296.9 1006.1,1296.9C996.8,1296.9 989.6,1304.1 989.6,1313.4C989.6,1322.6 996.8,1329.8 1006.1,1329.8Z" /> + android:pathData="M1054.3,1329.8C1063.5,1329.8 1071.2,1322.1 1071.2,1312.8C1071.2,1303.6 1063.5,1295.9 1054.3,1295.9C1045.1,1295.9 1037.4,1303.6 1037.4,1312.8C1037.4,1322.1 1045.1,1329.8 1054.3,1329.8Z" /> + android:pathData="M1103,1327.7C1111.2,1327.7 1117.9,1321.1 1117.9,1312.8C1117.9,1304.6 1111.2,1298 1103,1298C1094.8,1298 1088.2,1304.6 1088.2,1312.8C1088.2,1321.6 1094.8,1327.7 1103,1327.7Z" /> + android:pathData="M1151.3,1328.2C1159.5,1328.2 1166.6,1321.6 1166.6,1312.8C1166.6,1304.6 1160,1297.5 1151.3,1297.5C1142.5,1297.5 1135.9,1304.1 1135.9,1312.8C1136.4,1321.6 1143,1328.2 1151.3,1328.2Z" /> + android:pathData="M1200,1332.3C1210.3,1332.3 1219,1323.6 1219,1313.4C1219,1302.6 1210.3,1294.4 1200,1294.4C1189.2,1294.4 1181,1303.1 1181,1313.4C1180.5,1323.6 1189.2,1332.3 1200,1332.3Z" /> + android:pathData="M1248.2,1330.3C1258,1330.3 1265.7,1322.6 1265.7,1312.8C1265.7,1303.6 1258,1295.4 1248.2,1295.4C1239,1295.4 1231.3,1303.1 1231.3,1312.8C1230.8,1322.6 1238.5,1330.3 1248.2,1330.3Z" /> + android:pathData="M1296.5,1330.8C1306.2,1330.8 1313.9,1323.1 1313.9,1313.4C1313.9,1303.6 1306.2,1295.9 1296.5,1295.9C1286.7,1295.9 1279,1303.6 1279,1313.4C1279,1323.1 1286.7,1330.8 1296.5,1330.8Z" /> + android:pathData="M1345.2,1330.8C1354.9,1330.8 1362.6,1323.1 1362.6,1313.4C1362.6,1303.6 1354.9,1295.9 1345.2,1295.9C1335.4,1295.9 1327.7,1303.6 1327.7,1313.4C1327.7,1323.1 1335.4,1330.8 1345.2,1330.8Z" /> + android:pathData="M812.6,1382.1C823.9,1382.1 833.2,1372.9 833.2,1361.6C833.2,1350.3 823.9,1341.1 812.6,1341.1C801.4,1341.1 792.1,1350.3 792.1,1361.6C792.1,1372.9 801.4,1382.1 812.6,1382.1Z" /> + android:pathData="M909.1,1382.1C920.4,1382.1 929.6,1372.9 929.6,1361.6C929.6,1350.3 920.4,1341.1 909.1,1341.1C897.8,1341.1 888.6,1350.3 888.6,1361.6C889.1,1372.9 898.3,1382.1 909.1,1382.1Z" /> + android:pathData="M1054.3,1378.5C1063.5,1378.5 1071.2,1370.8 1071.2,1361.6C1071.2,1352.3 1063.5,1344.7 1054.3,1344.7C1045.1,1344.7 1037.4,1352.3 1037.4,1361.6C1037.9,1370.8 1045.1,1378.5 1054.3,1378.5Z" /> + android:pathData="M1103,1378.5C1112.3,1378.5 1120,1370.8 1120,1361.6C1120,1352.3 1112.3,1344.7 1103,1344.7C1093.8,1344.7 1086.1,1352.3 1086.1,1361.6C1086.1,1370.8 1093.3,1378.5 1103,1378.5Z" /> + android:pathData="M1200,1380.6C1210.3,1380.6 1219,1371.8 1219,1361.6C1219,1351.3 1210.3,1342.6 1200,1342.6C1189.2,1342.6 1181,1351.3 1181,1361.6C1180.5,1371.8 1189.2,1380.6 1200,1380.6Z" /> + android:pathData="M1248.2,1378.5C1258,1378.5 1265.7,1370.8 1265.7,1361.1C1265.7,1351.8 1258,1344.1 1248.2,1344.1C1239,1344.1 1231.3,1351.8 1231.3,1361.1C1230.8,1370.8 1238.5,1378.5 1248.2,1378.5Z" /> + android:pathData="M1296.5,1379C1306.2,1379 1313.9,1371.3 1313.9,1361.6C1313.9,1351.8 1306.2,1344.1 1296.5,1344.1C1286.7,1344.1 1279,1351.8 1279,1361.6C1279,1371.3 1286.7,1379 1296.5,1379Z" /> + android:pathData="M1345.2,1379C1354.9,1379 1362.6,1371.3 1362.6,1361.6C1362.6,1351.8 1354.9,1344.1 1345.2,1344.1C1335.4,1344.1 1327.7,1351.8 1327.7,1361.6C1327.2,1371.3 1335.4,1379 1345.2,1379Z" /> + android:pathData="M496,611.2C486.7,611.2 479.1,618.7 479.1,628C479.1,637.3 486.7,644.8 496,644.8C505.3,644.8 512.9,637.3 512.9,628C512.3,618.7 505.3,611.2 496,611.2Z" /> + android:pathData="M440.6,607.7C429.5,607.7 420.1,617 420.1,628C420.1,639 429.5,648.3 440.6,648.3C451.6,648.3 461,639 461,628C461,617 452.2,607.7 440.6,607.7Z" /> + android:pathData="M496,502.1C486.7,502.1 479.1,509.7 479.1,518.9C479.1,528.2 486.7,535.8 496,535.8C505.3,535.8 512.9,528.2 512.9,518.9C512.3,509.7 505.3,502.1 496,502.1Z" /> + android:pathData="M440.6,502.1C431.2,502.1 423.6,509.7 423.6,518.9C423.6,528.2 431.2,535.8 440.6,535.8C449.9,535.8 457.5,528.2 457.5,518.9C457.5,509.7 449.9,502.1 440.6,502.1Z" /> + android:pathData="M110.3,502.1C101,502.1 93.4,509.7 93.4,518.9C93.4,528.2 101,535.8 110.3,535.8C119.6,535.8 127.2,528.2 127.2,518.9C127.2,509.7 119.6,502.1 110.3,502.1Z" /> + android:pathData="M55.4,502.1C46.1,502.1 38.5,509.7 38.5,518.9C38.5,528.2 46.1,535.8 55.4,535.8C64.8,535.8 72.4,528.2 72.4,518.9C72.4,509.7 64.8,502.1 55.4,502.1Z" /> + android:pathData="M385.7,447C376.4,447 368.8,454.6 368.8,463.9C368.8,473.1 376.4,480.7 385.7,480.7C395,480.7 402.6,473.1 402.6,463.9C402.6,454.6 395,447 385.7,447Z" /> + android:pathData="M220.6,447C211.2,447 203.6,454.6 203.6,463.9C203.6,473.1 211.2,480.7 220.6,480.7C229.9,480.7 237.5,473.1 237.5,463.9C237.5,454.6 229.9,447 220.6,447Z" /> + android:pathData="M110.3,447C101,447 93.4,454.6 93.4,463.9C93.4,473.1 101,480.7 110.3,480.7C119.6,480.7 127.2,473.1 127.2,463.9C127.2,454.6 119.6,447 110.3,447Z" /> + android:fillColor="#4CA7D9" + android:pathData="M55.4,463.9m-17.5,0a17.4,17.5 90,1 1,35 0a17.4,17.5 90,1 1,-35 0" /> + android:pathData="M550.8,392.5C541.5,392.5 533.9,400.1 533.9,409.3C533.9,418.6 541.5,426.2 550.8,426.2C560.2,426.2 567.8,418.6 567.8,409.3C567.8,400.1 560.2,392.5 550.8,392.5Z" /> + android:pathData="M385.7,392.5C376.4,392.5 368.8,400.1 368.8,409.3C368.8,418.6 376.4,426.2 385.7,426.2C395,426.2 402.6,418.6 402.6,409.3C402.6,400.1 395,392.5 385.7,392.5Z" /> + android:pathData="M275.4,392.5C266.1,392.5 258.5,400.1 258.5,409.3C258.5,418.6 266.1,426.2 275.4,426.2C284.8,426.2 292.3,418.6 292.3,409.3C292.3,400.1 284.8,392.5 275.4,392.5Z" /> + android:pathData="M220.6,392.5C211.2,392.5 203.6,400.1 203.6,409.3C203.6,418.6 211.2,426.2 220.6,426.2C229.9,426.2 237.5,418.6 237.5,409.3C237.5,400.1 229.9,392.5 220.6,392.5Z" /> + android:pathData="M165.7,386.7C153.5,386.7 143,396.6 143,409.3C143,421.5 152.9,432 165.7,432C178,432 188.5,422.1 188.5,409.3C187.9,396.6 178,386.7 165.7,386.7Z" /> + android:pathData="M110.3,390.8C99.8,390.8 91.6,398.9 91.6,409.3C91.6,419.8 99.8,427.9 110.3,427.9C120.8,427.9 129,419.8 129,409.3C129,398.9 120.8,390.8 110.3,390.8Z" /> + android:pathData="M55.4,386.1C42.6,386.1 32.1,396.6 32.1,409.3C32.1,422.1 42.6,432.5 55.4,432.5C68.3,432.5 78.8,422.1 78.8,409.3C78.8,396.6 68.3,386.1 55.4,386.1Z" /> + android:pathData="M0.6,392.5C-8.7,392.5 -16.3,400.1 -16.3,409.3C-16.3,418.6 -8.7,426.2 0.6,426.2C9.9,426.2 17.5,418.6 17.5,409.3C16.9,400.1 9.3,392.5 0.6,392.5Z" /> + android:pathData="M550.8,338C541.5,338 533.9,345.6 533.9,354.8C533.9,364.1 541.5,371.6 550.8,371.6C560.2,371.6 567.8,364.1 567.8,354.8C567.8,345.6 560.2,338 550.8,338Z" /> + android:pathData="M440.6,338C431.2,338 423.6,345.6 423.6,354.8C423.6,364.1 431.2,371.6 440.6,371.6C449.9,371.6 457.5,364.1 457.5,354.8C457.5,345.6 449.9,338 440.6,338Z" /> + android:pathData="M385.7,338C376.4,338 368.8,345.6 368.8,354.8C368.8,364.1 376.4,371.6 385.7,371.6C395,371.6 402.6,364.1 402.6,354.8C402.6,345.6 395,338 385.7,338Z" /> + android:pathData="M330.9,336.9C320.9,336.9 312.8,345 312.8,354.8C312.8,364.7 320.9,372.8 330.9,372.8C340.8,372.8 348.9,364.7 348.9,354.8C348.4,344.4 340.8,336.9 330.9,336.9Z" /> + android:pathData="M275.4,331.1C262.6,331.1 252.1,341.5 252.1,354.2C252.1,367 262.6,377.4 275.4,377.4C288.3,377.4 298.8,367 298.8,354.2C298.8,341.5 288.3,331.1 275.4,331.1Z" /> + android:pathData="M220.6,338C211.2,338 203.6,345.6 203.6,354.8C203.6,364.1 211.2,371.6 220.6,371.6C229.9,371.6 237.5,364.1 237.5,354.8C237.5,345.6 229.9,338 220.6,338Z" /> + android:pathData="M165.7,338C156.4,338 148.8,345.6 148.8,354.8C148.8,364.1 156.4,371.6 165.7,371.6C175.1,371.6 182.6,364.1 182.6,354.8C182.1,345.6 175.1,338 165.7,338Z" /> + android:pathData="M110.3,336.9C100.4,336.9 92.8,344.4 92.8,354.2C92.8,364.1 101,371.6 110.3,371.6C120.2,371.6 127.8,364.1 127.8,354.2C127.8,345 120.2,336.9 110.3,336.9Z" /> + android:pathData="M55.4,335.7C44.9,335.7 36.8,343.8 36.8,354.2C36.8,364.7 44.9,372.8 55.4,372.8C65.9,372.8 74.1,364.7 74.1,354.2C74.1,344.4 65.9,335.7 55.4,335.7Z" /> + android:pathData="M0.6,338C-8.7,338 -16.3,345.6 -16.3,354.8C-16.3,364.1 -8.7,371.6 0.6,371.6C9.9,371.6 17.5,364.1 17.5,354.8C16.9,345.6 9.3,338 0.6,338Z" /> + android:pathData="M881.1,282.9C871.8,282.9 864.2,290.5 864.2,299.7C864.2,309 871.8,316.6 881.1,316.6C890.4,316.6 898,309 898,299.7C898,290.5 890.4,282.9 881.1,282.9Z" /> + android:pathData="M716,282.9C706.6,282.9 699,290.5 699,299.7C699,309 706.6,316.6 716,316.6C725.3,316.6 732.9,309 732.9,299.7C732.9,290.5 725.3,282.9 716,282.9Z" /> + android:pathData="M661.1,282.9C651.8,282.9 644.2,290.5 644.2,299.7C644.2,309 651.8,316.6 661.1,316.6C670.5,316.6 678,309 678,299.7C678,290.5 669.9,282.9 661.1,282.9Z" /> + android:pathData="M605.7,282.9C596.4,282.9 588.8,290.5 588.8,299.7C588.8,309 596.4,316.6 605.7,316.6C615,316.6 622.6,309 622.6,299.7C622.6,290.5 615,282.9 605.7,282.9Z" /> + android:pathData="M550.8,282.9C541.5,282.9 533.9,290.5 533.9,299.7C533.9,309 541.5,316.6 550.8,316.6C560.2,316.6 567.8,309 567.8,299.7C567.8,290.5 560.2,282.9 550.8,282.9Z" /> + android:pathData="M496,278.3C484.3,278.3 474.4,288.1 474.4,299.7C474.4,311.9 484.3,321.2 496,321.2C507.7,321.2 517.6,311.3 517.6,299.7C517,288.1 507.7,278.3 496,278.3Z" /> + android:pathData="M440.6,282.9C431.2,282.9 423.6,290.5 423.6,299.7C423.6,309 431.2,316.6 440.6,316.6C449.9,316.6 457.5,309 457.5,299.7C457.5,290.5 449.9,282.9 440.6,282.9Z" /> + android:pathData="M385.7,282.3C375.8,282.3 367.6,290.5 367.6,300.3C367.6,310.2 375.8,318.3 385.7,318.3C395.6,318.3 403.8,310.2 403.8,300.3C403.8,289.9 395.6,282.3 385.7,282.3Z" /> + android:pathData="M330.9,280.6C320.4,280.6 311.6,289.3 311.6,299.7C311.6,310.8 320.4,319.5 330.9,319.5C341.9,319.5 350.7,310.8 350.7,299.7C350.1,289.3 341.4,280.6 330.9,280.6Z" /> + android:pathData="M275.4,277.7C263.2,277.7 253.2,287.6 253.2,299.7C253.2,311.9 263.2,321.8 275.4,321.8C287.7,321.8 297.6,311.9 297.6,299.7C297.6,287.6 287.7,277.7 275.4,277.7Z" /> + android:pathData="M220.6,276.5C207.7,276.5 197.2,287 197.2,299.7C197.2,312.5 207.7,322.9 220.6,322.9C233.4,322.9 243.9,312.5 243.9,299.7C243.9,287 233.4,276.5 220.6,276.5Z" /> + android:pathData="M165.7,282.9C156.4,282.9 148.8,290.5 148.8,299.7C148.8,309 156.4,316.6 165.7,316.6C175.1,316.6 182.6,309 182.6,299.7C182.1,290.5 175.1,282.9 165.7,282.9Z" /> + android:pathData="M110.3,282.3C100.4,282.3 92.8,290.5 92.8,299.7C92.8,309.6 101,317.1 110.3,317.1C120.2,317.1 127.8,309.6 127.8,299.7C127.8,290.5 120.2,282.3 110.3,282.3Z" /> + android:pathData="M55.4,281.2C44.9,281.2 36.8,289.3 36.8,299.7C36.8,310.2 44.9,318.3 55.4,318.3C65.9,318.3 74.1,310.2 74.1,299.7C74.1,289.9 65.9,281.2 55.4,281.2Z" /> + android:pathData="M0.6,280.6C-9.9,280.6 -18.7,289.3 -18.7,299.7C-18.7,310.8 -9.9,319.5 0.6,319.5C11.7,319.5 20.4,310.8 20.4,299.7C19.8,289.3 11.1,280.6 0.6,280.6Z" /> + android:pathData="M661.1,228.4C651.8,228.4 644.2,235.9 644.2,245.2C644.2,254.5 651.8,262 661.1,262C670.5,262 678,254.5 678,245.2C678,235.9 669.9,228.4 661.1,228.4Z" /> + android:pathData="M605.7,228.4C596.4,228.4 588.8,235.9 588.8,245.2C588.8,254.5 596.4,262 605.7,262C615,262 622.6,254.5 622.6,245.2C623.2,235.9 615,228.4 605.7,228.4Z" /> + android:pathData="M550.8,228.4C541.5,228.4 533.9,235.9 533.9,245.2C533.9,254.5 541.5,262 550.8,262C560.2,262 567.8,254.5 567.8,245.2C567.8,235.9 560.2,228.4 550.8,228.4Z" /> + android:pathData="M275.4,173.9C266.1,173.9 258.5,181.4 258.5,190.7C258.5,200 266.1,207.5 275.4,207.5C284.8,207.5 292.3,200 292.3,190.7C292.3,181.4 284.8,173.9 275.4,173.9Z" /> + android:pathData="M220.6,173.9C211.2,173.9 203.6,181.4 203.6,190.7C203.6,200 211.2,207.5 220.6,207.5C229.9,207.5 237.5,200 237.5,190.7C237.5,181.4 229.9,173.9 220.6,173.9Z" /> + android:pathData="M165.7,170.4C154.6,170.4 145.3,179.7 145.3,190.7C145.3,201.7 154.1,211 165.7,211C176.8,211 186.1,201.7 186.1,190.7C185.6,179.1 176.8,170.4 165.7,170.4Z" /> + android:pathData="M440.6,118.8C431.2,118.8 423.6,126.3 423.6,135.6C423.6,144.9 431.2,152.4 440.6,152.4C449.9,152.4 457.5,144.9 457.5,135.6C457.5,126.3 449.9,118.8 440.6,118.8Z" /> + android:pathData="M330.9,118.8C321.5,118.8 313.9,126.3 313.9,135.6C313.9,144.9 321.5,152.4 330.9,152.4C340.2,152.4 347.8,144.9 347.8,135.6C347.2,126.3 339.6,118.8 330.9,118.8Z" /> + android:pathData="M275.4,118.8C266.1,118.8 258.5,126.3 258.5,135.6C258.5,144.9 266.1,152.4 275.4,152.4C284.8,152.4 292.3,144.9 292.3,135.6C292.3,126.3 284.8,118.8 275.4,118.8Z" /> + android:pathData="M220.6,118.8C211.2,118.8 203.1,126.3 203.1,136.2C203.1,145.5 210.7,153.6 220.6,153.6C229.9,153.6 238.1,146.1 238.1,136.2C238.1,126.3 229.9,118.8 220.6,118.8Z" /> + android:pathData="M165.7,115.9C154.6,115.9 145.3,124.6 145.3,136.2C145.3,147.2 154.1,156.5 165.7,156.5C176.8,156.5 186.1,147.2 186.1,136.2C185.6,124.6 176.8,115.9 165.7,115.9Z" /> + android:pathData="M110.3,118.8C101,118.8 93.4,126.3 93.4,135.6C93.4,144.9 101,152.4 110.3,152.4C119.6,152.4 127.2,144.9 127.2,135.6C127.2,126.3 119.6,118.8 110.3,118.8Z" /> + android:fillColor="#4CA7D9" + android:pathData="M55.4,135.6m-18.1,0a18,18.1 90,1 1,36.2 0a18,18.1 90,1 1,-36.2 0" /> + android:pathData="M0.6,118.8C-8.7,118.8 -16.3,126.3 -16.3,135.6C-16.3,144.9 -8.7,152.4 0.6,152.4C9.9,152.4 17.5,144.9 17.5,135.6C16.9,126.3 9.3,118.8 0.6,118.8Z" /> + android:pathData="M1101.1,64.3C1091.7,64.3 1084.2,71.8 1084.2,81.1C1084.2,90.4 1091.7,97.9 1101.1,97.9C1110.4,97.9 1118,90.4 1118,81.1C1118,71.8 1110.4,64.3 1101.1,64.3Z" /> + android:pathData="M1046.2,62C1035.7,62 1027,70.7 1027,81.1C1027,91.5 1035.7,100.2 1046.2,100.2C1056.7,100.2 1065.5,91.5 1065.5,81.1C1065.5,70.7 1056.7,62 1046.2,62Z" /> + android:pathData="M881.1,64.3C871.8,64.3 864.2,71.8 864.2,81.1C864.2,90.4 871.8,97.9 881.1,97.9C890.4,97.9 898,90.4 898,81.1C898,71.8 890.4,64.3 881.1,64.3Z" /> + android:pathData="M826.3,61.4C815.7,61.4 806.4,70.1 806.4,80.5C806.4,91.5 815.2,100.2 826.3,100.2C836.8,100.2 846.1,91.5 846.1,80.5C845.5,70.1 836.8,61.4 826.3,61.4Z" /> + android:pathData="M716,64.3C706.6,64.3 699,71.8 699,81.1C699,90.4 706.6,97.9 716,97.9C725.3,97.9 732.9,90.4 732.9,81.1C732.9,71.8 725.3,64.3 716,64.3Z" /> + android:pathData="M661.1,64.3C651.8,64.3 644.2,71.8 644.2,81.1C644.2,90.4 651.8,97.9 661.1,97.9C670.5,97.9 678,90.4 678,81.1C678,71.8 669.9,64.3 661.1,64.3Z" /> + android:pathData="M605.7,64.3C596.4,64.3 588.8,71.8 588.8,81.1C588.8,90.4 596.4,97.9 605.7,97.9C615,97.9 622.6,90.4 622.6,81.1C622.6,71.8 615,64.3 605.7,64.3Z" /> + android:pathData="M550.8,64.3C541.5,64.3 533.9,71.8 533.9,81.1C533.9,90.4 541.5,97.9 550.8,97.9C560.2,97.9 567.8,90.4 567.8,81.1C567.8,71.8 560.2,64.3 550.8,64.3Z" /> + android:pathData="M496,64.3C486.7,64.3 479.1,71.8 479.1,81.1C479.1,90.4 486.7,97.9 496,97.9C505.3,97.9 512.9,90.4 512.9,81.1C512.9,71.8 505.3,64.3 496,64.3Z" /> + android:pathData="M440.6,64.3C431.2,64.3 423.6,71.8 423.6,81.1C423.6,90.4 431.2,97.9 440.6,97.9C449.9,97.9 457.5,90.4 457.5,81.1C457.5,71.8 449.9,64.3 440.6,64.3Z" /> + android:pathData="M385.7,63.7C376.4,63.7 368.8,71.2 368.8,80.5C368.8,89.8 376.4,97.3 385.7,97.3C395,97.3 402.6,89.8 402.6,80.5C402.6,71.8 395,63.7 385.7,63.7Z" /> + android:pathData="M330.9,64.3C321.5,64.3 313.9,71.8 313.9,81.1C313.9,90.4 321.5,97.9 330.9,97.9C340.2,97.9 347.8,90.4 347.8,81.1C347.8,71.8 339.6,64.3 330.9,64.3Z" /> + android:pathData="M275.4,64.3C266.1,64.3 258.5,71.8 258.5,81.1C258.5,90.4 266.1,97.9 275.4,97.9C284.8,97.9 292.3,90.4 292.3,81.1C292.3,71.8 284.8,64.3 275.4,64.3Z" /> + android:pathData="M220.6,63.7C211.2,63.7 203.1,71.2 203.1,81.1C203.1,91 210.7,98.5 220.6,98.5C229.9,98.5 238.1,91 238.1,81.1C238.1,71.2 229.9,63.7 220.6,63.7Z" /> + android:pathData="M165.7,63.7C156.4,63.7 148.2,71.2 148.2,81.1C148.2,91 155.8,98.5 165.7,98.5C175.1,98.5 183.2,91 183.2,81.1C183.2,71.2 175.1,63.7 165.7,63.7Z" /> + android:pathData="M110.3,63.7C101,63.7 92.8,71.2 92.8,81.1C92.8,91 100.4,98.5 110.3,98.5C119.6,98.5 127.8,91 127.8,81.1C127.8,71.2 120.2,63.7 110.3,63.7Z" /> + android:pathData="M55.4,63.7C46.1,63.7 37.9,71.2 37.9,81.1C37.9,91 45.5,98.5 55.4,98.5C64.8,98.5 72.9,91 72.9,81.1C72.9,71.2 64.8,63.7 55.4,63.7Z" /> + android:pathData="M0.6,64.3C-8.7,64.3 -16.3,71.8 -16.3,81.1C-16.3,90.4 -8.7,97.9 0.6,97.9C9.9,97.9 17.5,90.4 17.5,81.1C17.5,71.8 9.3,64.3 0.6,64.3Z" /> + android:pathData="M1211.4,9.8C1202,9.8 1194.4,17.3 1194.4,26.6C1194.4,35.9 1202,43.4 1211.4,43.4C1220.7,43.4 1228.3,35.9 1228.3,26.6C1228.3,17.3 1220.7,9.8 1211.4,9.8Z" /> + android:pathData="M1156.5,9.8C1147.2,9.8 1139.6,17.3 1139.6,26.6C1139.6,35.9 1147.2,43.4 1156.5,43.4C1165.9,43.4 1173.4,35.9 1173.4,26.6C1172.9,17.3 1165.9,9.8 1156.5,9.8Z" /> + android:pathData="M550.8,9.8C541.5,9.8 533.9,17.3 533.9,26.6C533.9,35.9 541.5,43.4 550.8,43.4C560.2,43.4 567.8,35.9 567.8,26.6C567.8,17.3 560.2,9.8 550.8,9.8Z" /> + android:pathData="M496,9.8C486.7,9.8 479.1,17.3 479.1,26.6C479.1,35.9 486.7,43.4 496,43.4C505.3,43.4 512.9,35.9 512.9,26.6C512.3,17.3 505.3,9.8 496,9.8Z" /> diff --git a/app/src/main/res/drawable/nextcloud_foreground.xml b/app/src/main/res/drawable/nextcloud_foreground.xml index 1fb1aca4..fcd85d49 100644 --- a/app/src/main/res/drawable/nextcloud_foreground.xml +++ b/app/src/main/res/drawable/nextcloud_foreground.xml @@ -18,15 +18,16 @@ License along with this program. If not, see . --> - + android:width="108dp" + android:height="108dp" + android:viewportWidth="1636.9231" + android:viewportHeight="1636.9231"> + + android:pathData="M532.7,320C439.3,320 360.9,383.9 337,469.9 316.1,423.9 270.1,391.3 216.6,391.3 143.8,391.3 84,451.2 84,524c-0,72.8 59.8,132.6 132.6,132.7 53.5,-0 99.4,-32.6 120.4,-78.6 23.9,86 102.4,149.9 195.7,149.9 92.8,0 170.8,-63.2 195.3,-148.5 21.2,45.1 66.5,77.2 119.4,77.2 72.8,0 132.7,-59.8 132.6,-132.7 -0,-72.8 -59.9,-132.6 -132.6,-132.6 -52.8,0 -98.2,32 -119.4,77.2 -24.4,-85.3 -102.4,-148.5 -195.3,-148.5zM532.7,397.9c70.1,0 126.1,56 126.1,126.1 0,70.1 -56,126.1 -126.1,126.1 -70.1,-0 -126.1,-56 -126.1,-126.1 0,-70.1 56,-126.1 126.1,-126.1zM216.6,469.2c30.7,0 54.8,24.1 54.8,54.8 0,30.7 -24,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8zM847.4,469.2c30.7,-0 54.8,24.1 54.8,54.8 0,30.7 -24.1,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8z" /> diff --git a/app/src/main/res/layout/fragment_recovery_code_input.xml b/app/src/main/res/layout/fragment_recovery_code_input.xml index 7996bcb8..09e421e5 100644 --- a/app/src/main/res/layout/fragment_recovery_code_input.xml +++ b/app/src/main/res/layout/fragment_recovery_code_input.xml @@ -81,9 +81,9 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/doneButton" app:layout_constraintHorizontal_bias="0.0" - app:layout_constraintVertical_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/wordList" + app:layout_constraintVertical_bias="1.0" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/fragment_restore_progress.xml b/app/src/main/res/layout/fragment_restore_progress.xml index 3254843d..4bd34495 100644 --- a/app/src/main/res/layout/fragment_restore_progress.xml +++ b/app/src/main/res/layout/fragment_restore_progress.xml @@ -62,8 +62,8 @@ android:layout_marginBottom="16dp" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@+id/backupNameView" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/backupNameView" tools:listitem="@layout/list_item_app_status" />