diff --git a/.idea/runConfigurations/Instrumentation_tests__app.xml b/.idea/runConfigurations/Instrumentation_tests__app.xml index b7b22097..4124cb1d 100644 --- a/.idea/runConfigurations/Instrumentation_tests__app.xml +++ b/.idea/runConfigurations/Instrumentation_tests__app.xml @@ -1,6 +1,6 @@ - + - + \ No newline at end of file diff --git a/Android.bp b/Android.bp index ba382399..04c3cc12 100644 --- a/Android.bp +++ b/Android.bp @@ -26,12 +26,16 @@ android_app { static_libs: [ "kotlin-stdlib-jdk8", "androidx.core_core-ktx", + "androidx.fragment_fragment-ktx", + "androidx.activity_activity-ktx", "androidx.preference_preference", "androidx.documentfile_documentfile", "androidx.lifecycle_lifecycle-viewmodel-ktx", "androidx.lifecycle_lifecycle-livedata-ktx", "androidx-constraintlayout_constraintlayout", "com.google.android.material_material", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", // storage backup lib "seedvault-lib-storage", // koin @@ -48,8 +52,9 @@ android_app { privileged: true, required: [ "LocalContactsBackup", - "privapp_whitelist_com.stevesoltys.backup", - "com.stevesoltys.backup_whitelist" + "com.stevesoltys.backup_allowlist", + "com.stevesoltys.backup_default-permissions", + "com.stevesoltys.backup_privapp_allowlist" ], optimize: { enabled: false, @@ -57,17 +62,25 @@ android_app { } prebuilt_etc { - name: "privapp_whitelist_com.stevesoltys.backup", + name: "com.stevesoltys.backup_allowlist", + system_ext_specific: true, + sub_dir: "sysconfig", + src: "allowlist_com.stevesoltys.seedvault.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "com.stevesoltys.backup_default-permissions", + system_ext_specific: true, + sub_dir: "default-permissions", + src: "default-permissions_com.stevesoltys.seedvault.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "com.stevesoltys.backup_privapp_allowlist", system_ext_specific: true, sub_dir: "permissions", src: "permissions_com.stevesoltys.seedvault.xml", filename_from_src: true, } - -prebuilt_etc { - name: "com.stevesoltys.backup_whitelist", - system_ext_specific: true, - sub_dir: "sysconfig", - src: "whitelist_com.stevesoltys.seedvault.xml", - filename_from_src: true, -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b41811..c08347fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [13-3.1] - 2022-09-01 +* Initial release for Android 13 +* Don't attempt to restore app that is used as a backup location (e.g. Nextcloud), + because can cause restore to abort early +* Upgrade several libraries + ## [12-3.0] - 2021-10-13 * Initial release for Android 12 * Use the same (faster and more secure) crypto that storage backups use, diff --git a/README.md b/README.md index 41ccd2e9..1f53e0d9 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,19 @@ If you are having an issue/question, please look at our [FAQ](../../wiki/FAQ). ## Requirements -- Android 12 +SeedVault is developed alongwith AOSP releases + +We update it every time Google releases a new Android version, make any changes required for basic functionality, and any improvements possible through API changes in the OS. + +This means that for ROMs using SeedVault it's recommended to use the same branch as your android version + +- This current branch `android13` is meant for usage with Android 13 +- This is indicated by the version name starting with `13`, and the version code starting with `33` - the Android 13 API version For older versions of Android, check out [the branches](https://github.com/seedvault-app/seedvault/branches). +Trying to use an older branch on a newer version may lead to issues and is not something we can support. + ## Getting Started - Check out [the wiki](https://github.com/seedvault-app/seedvault/wiki) for information on building the application with AOSP. @@ -44,6 +53,7 @@ It uses the same internal APIs as `adb backup` which is deprecated and thus need * `android.permission.MANAGE_DOCUMENTS` to retrieve the available storage roots (optional) for better UX. * `android.permission.USE_BIOMETRIC` to authenticate saving a new recovery code * `android.permission.INTERACT_ACROSS_USERS_FULL` to use storage roots in other users (optional). +* `android.permission.POST_NOTIFICATIONS` to inform users about backup status and errors. ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault. diff --git a/whitelist_com.stevesoltys.seedvault.xml b/allowlist_com.stevesoltys.seedvault.xml similarity index 100% rename from whitelist_com.stevesoltys.seedvault.xml rename to allowlist_com.stevesoltys.seedvault.xml diff --git a/app/build.gradle b/app/build.gradle index d4134c83..675b1dd0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 29 // leave at 29 for robolectric tests + minSdkVersion 32 // leave at 32 for robolectric tests targetSdkVersion rootProject.ext.targetSdkVersion versionNameSuffix "-$gitDescribe" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -38,12 +38,12 @@ android { abortOnError true } compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - languageVersion = "1.4" + jvmTarget = JavaVersion.VERSION_11.toString() + languageVersion = "1.6" } testOptions { unitTests.all { @@ -122,13 +122,13 @@ dependencies { * You can copy these libraries from ~/.gradle/caches/modules-2/files-2.1 */ // later versions than 2.1.1 require newer kotlin version -// implementation "io.insert-koin:koin-core-jvm:3.1.2" -// implementation "io.insert-koin:koin-android:3.1.2" +// implementation "io.insert-koin:koin-core-jvm:3.2.0" +// implementation "io.insert-koin:koin-android:3.2.0" implementation fileTree(include: ['*.jar'], dir: "${rootProject.rootDir}/libs/koin-android") implementation fileTree(include: ['*.aar'], dir: "${rootProject.rootDir}/libs/koin-android") -// implementation "cash.z.ecc.android:kotlin-bip39:1.0.2" - implementation fileTree(include: ['kotlin-bip39-1.0.2.jar'], dir: "${rootProject.rootDir}/libs") +// implementation "cash.z.ecc.android:kotlin-bip39:1.0.4" + implementation fileTree(include: ['kotlin-bip39-jvm-1.0.4.jar'], dir: "${rootProject.rootDir}/libs") /** * Test Dependencies (do not concern the AOSP build) @@ -138,10 +138,11 @@ dependencies { // anything less than 'implementation' fails tests run with gradlew testImplementation rootProject.ext.aosp_libs testImplementation 'androidx.test.ext:junit:1.1.3' - testImplementation('org.robolectric:robolectric:4.3.1') { // 4.4 has issue with non-idle Looper + testImplementation('org.robolectric:robolectric:4.8.1') { // https://github.com/robolectric/robolectric/issues/5245 exclude group: "com.google.auto.service", module: "auto-service" } + testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5_version" testImplementation "org.junit.jupiter:junit-jupiter-params:$junit5_version" testImplementation "io.mockk:mockk:$mockk_version" @@ -159,9 +160,6 @@ apply from: "${rootProject.rootDir}/gradle/ktlint.gradle" gradle.projectsEvaluated { tasks.withType(JavaCompile) { - if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { - options.compilerArgs.addAll(['--release', '8']) - } options.compilerArgs.add('-Xbootclasspath/p:app/libs/android.jar:app/libs/libcore.jar') } } diff --git a/app/libs/android.jar b/app/libs/android.jar index 48002e33..5e9d53d4 100644 Binary files a/app/libs/android.jar and b/app/libs/android.jar differ diff --git a/app/libs/libcore.jar b/app/libs/libcore.jar index 94d798d2..2c0049b7 100644 Binary files a/app/libs/libcore.jar and b/app/libs/libcore.jar differ diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 09836263..0888d2f4 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -7,13 +7,11 @@ + tools:remove="android:permission" /> + tools:remove="android:permission" /> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fcb13f7..12b9e1d9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="33000301" + android:versionName="13-3.1"> - + + + + @@ -50,6 +54,16 @@ + + + + + + - - - { appStatus.visibility = INVISIBLE progressBar.visibility = VISIBLE - if (SDK_INT >= 30) { - progressBar.stateDescription = - context.getString(R.string.restore_app_status_installing) - } + progressBar.stateDescription = + context.getString(R.string.restore_app_status_installing) } SUCCEEDED -> { appStatus.setImageResource(R.drawable.ic_check_green) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt index cae5c319..29cb923c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt @@ -106,7 +106,7 @@ internal class AppListRetriever( time = time, status = status ) - }.sortedBy { it.name.toLowerCase(locale) } + }.sortedBy { it.name.lowercase(locale) } } private fun getNotAllowedApps(): List { @@ -120,7 +120,7 @@ internal class AppListRetriever( time = 0, status = FAILED_NOT_ALLOWED ) - }.sortedBy { it.name.toLowerCase(locale) } + }.sortedBy { it.name.lowercase(locale) } } private fun getIcon(packageName: String): Drawable = when (packageName) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt index 05164d78..f4a2effd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt @@ -1,15 +1,35 @@ package com.stevesoltys.seedvault.settings import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContracts.CreateDocument +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.permitDiskReads +import com.stevesoltys.seedvault.transport.backup.PackageService +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class ExpertSettingsFragment : PreferenceFragmentCompat() { + + private val viewModel: SettingsViewModel by sharedViewModel() + private val packageService: PackageService by inject() + // TODO set mimeType when upgrading androidx lib + private val createFileLauncher = registerForActivityResult(CreateDocument()) { uri -> + viewModel.onLogcatUriReceived(uri) + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { permitDiskReads { setPreferencesFromResource(R.xml.settings_expert, rootKey) } + findPreference("logcat")?.setOnPreferenceClickListener { + val versionName = packageService.getVersionName(requireContext().packageName) ?: "ver" + val timestamp = System.currentTimeMillis() + val name = "seedvault-$versionName-$timestamp.txt" + createFileLauncher.launch(name) + true + } } override fun onStart() { 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 ff26c3a8..227e84d4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -105,7 +105,7 @@ class SettingsFragment : PreferenceFragmentCompat() { dialog.dismiss() } .setNegativeButton(R.string.settings_backup_apk_dialog_disable) { dialog, _ -> - apkBackup.isChecked = enable + apkBackup.isChecked = false dialog.dismiss() } .show() @@ -130,14 +130,14 @@ class SettingsFragment : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.lastBackupTime.observe(viewLifecycleOwner, { time -> + viewModel.lastBackupTime.observe(viewLifecycleOwner) { time -> setAppBackupStatusSummary(time) - }) + } val backupFiles: Preference = findPreference("backup_files")!! - viewModel.filesSummary.observe(viewLifecycleOwner, { summary -> + viewModel.filesSummary.observe(viewLifecycleOwner) { summary -> backupFiles.summary = summary - }) + } } override fun onStart() { @@ -160,10 +160,10 @@ class SettingsFragment : PreferenceFragmentCompat() { if (resources.getBoolean(R.bool.show_restore_in_settings)) { menuRestore?.isVisible = true } - viewModel.backupPossible.observe(viewLifecycleOwner, { possible -> + viewModel.backupPossible.observe(viewLifecycleOwner) { possible -> menuBackupNow?.isEnabled = possible menuRestore?.isEnabled = possible - }) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index 8da4f9fc..3c190651 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -11,6 +11,7 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import android.net.Uri +import android.os.Process.myUid import android.provider.Settings import android.util.Log import android.widget.Toast @@ -35,8 +36,11 @@ import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService +import java.io.IOException +import java.lang.Runtime.getRuntime import java.util.concurrent.TimeUnit.HOURS private const val TAG = "SettingsViewModel" @@ -193,9 +197,9 @@ internal class SettingsViewModel( @UiThread fun loadFilesSummary() = viewModelScope.launch { val uriSummary = storageBackup.getUriSummaryString() - _filesSummary.value = if (uriSummary.isEmpty()) { + _filesSummary.value = uriSummary.ifEmpty { app.getString(R.string.settings_backup_files_summary) - } else uriSummary + } } /** @@ -233,4 +237,28 @@ internal class SettingsViewModel( BackupJobService.cancelJob(app) } + fun onLogcatUriReceived(uri: Uri?) = viewModelScope.launch(Dispatchers.IO) { + if (uri == null) { + onLogcatError() + return@launch + } + // 1000 is system uid, needed to get backup logs from the OS code. + val command = "logcat -d --uid=1000,${myUid()} *:V" + try { + app.contentResolver.openOutputStream(uri, "wt")?.use { outputStream -> + getRuntime().exec(command).inputStream.use { inputStream -> + inputStream.copyTo(outputStream) + } + } ?: throw IOException("OutputStream was null") + } catch (e: Exception) { + Log.e(TAG, "Error saving logcat ", e) + onLogcatError() + } + } + + private suspend fun onLogcatError() = withContext(Dispatchers.Main) { + val str = app.getString(R.string.settings_expert_logcat_error) + Toast.makeText(app, str, LENGTH_LONG).show() + } + } 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 60c9c9aa..02208eb0 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 @@ -154,7 +154,8 @@ internal class ApkBackup( streamGetter: suspend (name: String) -> OutputStream, ): List { check(packageInfo.splitNames != null) - val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs + // attention: though not documented, splitSourceDirs can be null + val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs ?: emptyArray() check(packageInfo.splitNames.size == splitSourceDirs.size) { "Size Mismatch! ${packageInfo.splitNames.size} != ${splitSourceDirs.size} " + "splitNames is ${packageInfo.splitNames.toList()}, " + 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 ac779830..5d791fc4 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 @@ -381,7 +381,13 @@ internal class BackupCoordinator( } // hook in here to back up APKs of apps that are otherwise not allowed for backup if (isPmBackup && settingsManager.canDoBackupNow()) { - backUpApksOfNotBackedUpPackages() + try { + backUpApksOfNotBackedUpPackages() + } catch (e: Exception) { + Log.e(TAG, "Error backing up APKs of opt-out apps: ", e) + // We are re-throwing this, because we want to know about problems here + throw e + } } } result 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 dd89cad8..060f5431 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 @@ -209,6 +209,7 @@ internal class KVBackup( else state.db.close() TRANSPORT_OK } catch (e: IOException) { + Log.e(TAG, "Error uploading DB", e) TRANSPORT_ERROR } finally { this.state = null diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt index e17800d7..37686fc4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt @@ -2,7 +2,6 @@ package com.stevesoltys.seedvault.ui import android.content.Context import android.content.pm.PackageManager -import android.os.Build.VERSION.SDK_INT import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE @@ -40,12 +39,10 @@ internal abstract class AppViewHolder(protected val v: View) : RecyclerView.View appInfo.visibility = GONE appStatus.visibility = INVISIBLE progressBar.visibility = VISIBLE - if (SDK_INT >= 30) { - progressBar.stateDescription = context.getString( - if (isRestore) R.string.restore_restoring - else R.string.backup_app_in_progress - ) - } + progressBar.stateDescription = context.getString( + if (isRestore) R.string.restore_restoring + else R.string.backup_app_in_progress + ) } else { appStatus.visibility = VISIBLE progressBar.visibility = INVISIBLE diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt index 6eb60079..d35971fc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt @@ -43,7 +43,7 @@ internal class NotificationBackupObserver( * @param currentBackupPackage The name of the package that now being backed up. * @param backupProgress Current progress of backup for the package. */ - override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) { + override fun onUpdate(currentBackupPackage: String?, backupProgress: BackupProgress) { showProgressNotification(currentBackupPackage) } @@ -57,7 +57,7 @@ internal class NotificationBackupObserver( * that was initialized * @param status Zero on success; a nonzero error code if the backup operation failed. */ - override fun onResult(target: String, status: Int) { + override fun onResult(target: String?, status: Int) { if (isLoggable(TAG, INFO)) { Log.i(TAG, "Completed. Target: $target, status: $status") } @@ -81,8 +81,8 @@ internal class NotificationBackupObserver( nm.onBackupFinished(success, numBackedUp) } - private fun showProgressNotification(packageName: String) { - if (currentPackage == packageName) return + private fun showProgressNotification(packageName: String?) { + if (packageName == null || currentPackage == packageName) return if (isLoggable(TAG, INFO)) { "Showing progress notification for $currentPackage $numPackages/$expectedPackages".let { 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 a6b3d3ed..5923a145 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 @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.MenuItem import android.view.WindowManager.LayoutParams.FLAG_SECURE import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.isDebugBuild import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import org.koin.androidx.viewmodel.ext.android.viewModel @@ -15,7 +16,7 @@ class RecoveryCodeActivity : BackupActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - window.addFlags(FLAG_SECURE) + if (!isDebugBuild()) window.addFlags(FLAG_SECURE) setContentView(R.layout.activity_recovery_code) 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 f0bc993a..a0d507a6 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 @@ -6,7 +6,6 @@ import android.content.Intent import android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG import android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL import android.hardware.biometrics.BiometricPrompt -import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.CancellationSignal import android.view.LayoutInflater @@ -22,7 +21,6 @@ import android.widget.TextView import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat.getMainExecutor @@ -122,9 +120,9 @@ class RecoveryCodeInputFragment : Fragment() { newCodeButton.visibility = if (forStoringNewCode) GONE else VISIBLE newCodeButton.setOnClickListener { generateNewCode() } - viewModel.existingCodeChecked.observeEvent(viewLifecycleOwner, { verified -> + viewModel.existingCodeChecked.observeEvent(viewLifecycleOwner) { verified -> onExistingCodeChecked(verified) - }) + } if (forStoringNewCode && isDebugBuild() && !viewModel.isRestore) debugPreFill() } @@ -147,7 +145,7 @@ class RecoveryCodeInputFragment : Fragment() { } if (forStoringNewCode) { val keyguardManager = requireContext().getSystemService(KeyguardManager::class.java) - if (SDK_INT >= 30 && keyguardManager.isDeviceSecure) { + if (keyguardManager.isDeviceSecure) { // if we have a lock-screen secret, we can ask for it before storing the code storeNewCodeAfterAuth(input) } else { @@ -159,7 +157,6 @@ class RecoveryCodeInputFragment : Fragment() { } } - @RequiresApi(30) private fun storeNewCodeAfterAuth(input: List) { val context = requireContext() val biometricPrompt = BiometricPrompt.Builder(context) diff --git a/app/src/main/res/drawable/ic_bug_report.xml b/app/src/main/res/drawable/ic_bug_report.xml new file mode 100644 index 00000000..dd702e24 --- /dev/null +++ b/app/src/main/res/drawable/ic_bug_report.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3ef5057..66e1800f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,9 +47,11 @@ Unlimited app quota Do not impose a limitation on the size of app backups.\n\nWarning: This can fill up your storage location quickly. Not needed for most apps. Save app log + + Could not save app log Sending this to the developers can help diagnose bugs.\n\nAlways review it to ensure it has no personally identifiable info, and delete it afterwards! Could not save app log - + Choose where to store backups Where to find your backups? diff --git a/app/src/main/res/xml/settings_expert.xml b/app/src/main/res/xml/settings_expert.xml index a257d898..11e8497c 100644 --- a/app/src/main/res/xml/settings_expert.xml +++ b/app/src/main/res/xml/settings_expert.xml @@ -5,4 +5,9 @@ android:key="unlimited_quota" android:summary="@string/settings_expert_quota_summary" android:title="@string/settings_expert_quota_title" /> - \ No newline at end of file + + diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt index 0e120ff2..9683ad06 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt @@ -41,7 +41,7 @@ import kotlin.random.Random @Suppress("DEPRECATION") @RunWith(AndroidJUnit4::class) @Config( - sdk = [29], // robolectric does not support 30, yet + sdk = [32], // robolectric does not support 33, yet application = TestApp::class ) class MetadataManagerTest { diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt index b7346b2c..bc3443c6 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt @@ -19,7 +19,7 @@ import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @Config( - sdk = [29], // robolectric does not support 30, yet + sdk = [32], // robolectric does not support 33, yet application = TestApp::class ) internal class DocumentFileTest { diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt index 97012e3d..f8805c78 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt @@ -133,14 +133,8 @@ internal class ApkBackupRestoreTest : TransportTest() { every { strictContext.cacheDir } returns tmpFile every { crypto.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns inputStream - every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo - every { - @Suppress("UNRESOLVED_REFERENCE") - pm.loadItemIcon( - packageInfo.applicationInfo, - packageInfo.applicationInfo - ) - } returns icon + every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo + every { applicationInfo.loadIcon(pm) } returns icon every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName every { splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName)) diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt index ad730402..6eb1e969 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt @@ -1,7 +1,6 @@ package com.stevesoltys.seedvault.restore.install import android.content.Context -import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo.FLAG_INSTALLED import android.content.pm.ApplicationInfo.FLAG_SYSTEM import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP @@ -112,7 +111,7 @@ internal class ApkRestoreTest : TransportTest() { every { strictContext.cacheDir } returns File(tmpDir.toString()) every { crypto.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream - every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo + every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo every { storagePlugin.providerPackageName } returns storageProviderPackageName apkRestore.restore(backup).collectIndexed { i, value -> @@ -176,14 +175,8 @@ internal class ApkRestoreTest : TransportTest() { coEvery { legacyStoragePlugin.getApkInputStream(token, packageName, "") } returns apkInputStream - every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo - every { - @Suppress("UNRESOLVED_REFERENCE") - pm.loadItemIcon( - packageInfo.applicationInfo, - packageInfo.applicationInfo - ) - } returns icon + every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo + every { applicationInfo.loadIcon(pm) } returns icon every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName coEvery { apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) @@ -200,13 +193,11 @@ internal class ApkRestoreTest : TransportTest() { runBlocking { val packageMetadata = this@ApkRestoreTest.packageMetadata.copy(system = true) packageMetadataMap[packageName] = packageMetadata - packageInfo.applicationInfo = mockk() val installedPackageInfo: PackageInfo = mockk() val willFail = Random.nextBoolean() val isSystemApp = Random.nextBoolean() cacheBaseApkAndGetInfo(tmpDir) - every { packageInfo.applicationInfo.loadIcon(pm) } returns icon every { storagePlugin.providerPackageName } returns storageProviderPackageName if (willFail) { @@ -214,7 +205,7 @@ internal class ApkRestoreTest : TransportTest() { pm.getPackageInfo(packageName, 0) } throws PackageManager.NameNotFoundException() } else { - installedPackageInfo.applicationInfo = ApplicationInfo().apply { + installedPackageInfo.applicationInfo = mockk { flags = if (!isSystemApp) FLAG_INSTALLED else FLAG_SYSTEM or FLAG_UPDATED_SYSTEM_APP } @@ -421,14 +412,8 @@ internal class ApkRestoreTest : TransportTest() { every { strictContext.cacheDir } returns File(tmpDir.toString()) every { crypto.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream - every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo - every { - @Suppress("UNRESOLVED_REFERENCE") - pm.loadItemIcon( - packageInfo.applicationInfo, - packageInfo.applicationInfo - ) - } returns icon + every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo + every { applicationInfo.loadIcon(pm) } returns icon every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName } diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt index 414f5c1d..8d93b053 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt @@ -22,7 +22,7 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @Config( - sdk = [29], // robolectric does not support 30, yet + sdk = [32], // robolectric does not support 33, yet application = TestApp::class ) internal class DeviceInfoTest { @@ -62,12 +62,10 @@ internal class DeviceInfoTest { assertFalse(deviceInfo.isSupportedLanguage("de")) // test areUnknownSplitsAllowed - val deviceName = "unknown robolectric" + assertTrue(deviceInfo.areUnknownSplitsAllowed("robolectric robolectric")) if (onlyOnSameDevice) { - assertTrue(deviceInfo.areUnknownSplitsAllowed(deviceName)) assertFalse(deviceInfo.areUnknownSplitsAllowed("foo bar")) } else { - assertTrue(deviceInfo.areUnknownSplitsAllowed(deviceName)) assertTrue(deviceInfo.areUnknownSplitsAllowed("foo bar")) } } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt index 2f4e0cd2..35e81b28 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt @@ -37,12 +37,13 @@ internal abstract class TransportTest { protected val sigInfo: SigningInfo = mockk() protected val token = Random.nextLong() + protected val applicationInfo = mockk { + flags = FLAG_ALLOW_BACKUP or FLAG_INSTALLED + } protected val packageInfo = PackageInfo().apply { packageName = "org.example" longVersionCode = Random.nextLong() - applicationInfo = ApplicationInfo().apply { - flags = FLAG_ALLOW_BACKUP or FLAG_INSTALLED - } + applicationInfo = this@TransportTest.applicationInfo signingInfo = sigInfo } protected val pmPackageInfo = PackageInfo().apply { diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt index 9a58f3fb..d5facae7 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt @@ -5,7 +5,6 @@ import android.app.backup.BackupTransport.TRANSPORT_NOT_INITIALIZED import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED -import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo.FLAG_STOPPED import android.content.pm.PackageInfo import android.net.Uri @@ -399,7 +398,9 @@ internal class BackupCoordinatorTest : BackupTest() { PackageInfo().apply { packageName = "org.example.2" // the second package does not get backed up, because it is stopped - applicationInfo = ApplicationInfo().apply { flags = FLAG_STOPPED } + applicationInfo = mockk { + flags = FLAG_STOPPED + } } ) val packageMetadata: PackageMetadata = mockk() diff --git a/build.gradle b/build.gradle index bb25f307..6c90483f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - // 1.3.21 Android 10 // 1.3.61 Android 11 // 1.4.30 Android 12 + // 1.6.10 Android 13 // Check: - // https://android.googlesource.com/platform/external/kotlinc/+/refs/tags/android-12.0.0_r2/build.txt - ext.aosp_kotlin_version = '1.4.31' // 1.4.30 breaks Kotlin plugin in Android Studio - ext.kotlin_version = '1.4.31' + // https://android.googlesource.com/platform/external/kotlinc/+/refs/tags/android-13.0.0_r3/build.txt + ext.aosp_kotlin_version = '1.6.10' // 1.6.10-release-923 in AOSP + ext.kotlin_version = '1.6.10' repositories { mavenCentral() @@ -15,15 +15,15 @@ buildscript { //noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.17" - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.2.2' } } ext { - buildToolsVersion = '31.0.0' - compileSdkVersion = 31 - minSdkVersion = 29 - targetSdkVersion = 31 + buildToolsVersion = '33.0.0' + compileSdkVersion = 33 + minSdkVersion = 32 + targetSdkVersion = 33 } apply from: 'gradle/dependencies.gradle' diff --git a/contactsbackup/build.gradle b/contactsbackup/build.gradle index c7999696..71121216 100644 --- a/contactsbackup/build.gradle +++ b/contactsbackup/build.gradle @@ -15,12 +15,12 @@ android { } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_11.toString() } testOptions { diff --git a/contactsbackup/libs/com.android.vcard.jar b/contactsbackup/libs/com.android.vcard.jar index f677ef61..8060d849 100644 Binary files a/contactsbackup/libs/com.android.vcard.jar and b/contactsbackup/libs/com.android.vcard.jar differ diff --git a/contactsbackup/src/main/AndroidManifest.xml b/contactsbackup/src/main/AndroidManifest.xml index a45b985d..6ce63f37 100644 --- a/contactsbackup/src/main/AndroidManifest.xml +++ b/contactsbackup/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="33000301" + android:versionName="13-3.1"> + + + diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 607af4e0..29a1f0aa 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -1,11 +1,13 @@ ext { - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#2943 - ext.room_version = "2.3.0-beta02" - // https://android.googlesource.com/platform/external/protobuf/+/refs/tags/android-12.0.0_r2/java/pom.xml#7 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#3901 + ext.room_version = "2.4.0-alpha05" + // https://android.googlesource.com/platform/external/protobuf/+/refs/tags/android-13.0.0_r3/java/pom.xml#7 ext.protobuf_version = "3.9.1" + + // test dependencies below - these do not care about AOSP and can be freely updated junit4_version = "4.13.2" - junit5_version = "5.5.2" // careful, upgrading this can change a Cipher's IV size in tests!? - mockk_version = "1.12.0" + junit5_version = "5.7.2" // careful, upgrading this can change a Cipher's IV size in tests!? + mockk_version = "1.12.3" espresso_version = "3.4.0" } @@ -37,63 +39,63 @@ ext.kotlin_libs = [ ], coroutines: [ dependencies.create('org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm') { - // https://android.googlesource.com/platform/prebuilts/tools/+/refs/tags/android-12.0.0_r2/common/m2/Android.bp#273 - version { strictly '1.4.2' } + // https://android.googlesource.com/platform/external/kotlinx.coroutines/+/refs/tags/android-13.0.0_r3/CHANGES.md + version { strictly '1.5.2' } }, dependencies.create('org.jetbrains.kotlinx:kotlinx-coroutines-android') { - // https://android.googlesource.com/platform/prebuilts/tools/+/refs/tags/android-12.0.0_r2/common/m2/Android.bp#288 - version { strictly '1.3.0' } + // https://android.googlesource.com/platform/external/kotlinx.coroutines/+/refs/tags/android-13.0.0_r3/CHANGES.md + version { strictly '1.5.2' } }, ], ] ext.std_libs = [ androidx_core: [ - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#867 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#1761 dependencies.create('androidx.core:core') { - version { strictly '1.6.0' } // should be 1.6.0-beta03, but that is not even released, yet + version { strictly '1.9.0-alpha03' } }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#833 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#1727 dependencies.create('androidx.core:core-ktx') { - version { strictly '1.5.0-beta02' } + version { strictly '1.9.0-alpha03' } }, ], - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#1189 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#2159 androidx_fragment: dependencies.create('androidx.fragment:fragment-ktx') { - version { strictly '1.4.0-alpha01' } + version { strictly '1.4.0-alpha09' } }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#20 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#57 androidx_activity: dependencies.create('androidx.activity:activity-ktx') { - version { strictly '1.3.0-alpha03' } + version { strictly '1.4.0-alpha02' } }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#2695 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#3597 androidx_preference: dependencies.create('androidx.preference:preference') { - version { strictly '1.1.1' } // should be 1.2.0-alpha01, but that is not even released, yet + version { strictly '1.2.0-alpha01' } }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#1820 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#2754 androidx_lifecycle_viewmodel_ktx: dependencies.create('androidx.lifecycle:lifecycle-viewmodel-ktx') { - version { strictly '2.4.0-alpha01' } + version { strictly '2.4.0-alpha03' } // 2.4.0-alpha04 in AOSP but was never released }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#1618 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#2550 androidx_lifecycle_livedata_ktx: dependencies.create('androidx.lifecycle:lifecycle-livedata-ktx') { - version { strictly '2.4.0-alpha01' } + version { strictly '2.4.0-alpha03' } // 2.4.0-alpha04 in AOSP but was never released }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/extras/constraint-layout-x/Android.bp#39 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/extras/constraint-layout-x/Android.bp#39 androidx_constraintlayout: dependencies.create('androidx.constraintlayout:constraintlayout') { version { strictly '2.0.0-beta7' } }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#969 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#1865 androidx_documentfile: dependencies.create('androidx.documentfile:documentfile') { - version { strictly '1.1.0-alpha01' } + version { strictly '1.1.0-alpha01' } // 1.1.0-alpha02 in AOSP but not released yet }, - // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/extras/material-design-x/Android.bp#6 + // https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/extras/material-design-x/Android.bp#6 com_google_android_material: dependencies.create('com.google.android.material:material') { - version { strictly '1.4.0' } + version { strictly '1.6.0-alpha03' } // 1.6.0-alpha0301 in AOSP }, ] ext.lint_libs = [ - exceptions: 'com.github.thirdegg:lint-rules:0.0.6-beta' + exceptions: 'com.github.thirdegg:lint-rules:0.1.0' ] ext.storage_libs = [ @@ -103,7 +105,9 @@ ext.storage_libs = [ com_google_protobuf_javalite: dependencies.create('com.google.protobuf:protobuf-javalite') { version { strictly "$protobuf_version" } }, + // https://github.com/google/tink/releases com_google_crypto_tink_android: dependencies.create('com.google.crypto.tink:tink-android') { - version { strictly '1.6.1' } + // careful with upgrading tink, so old backups continue to be decryptable + version { strictly '1.7.0' } }, ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20d1c07e..6b46f328 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -#Tue Aug 04 14:40:48 BRT 2020 +#Fri Aug 19 10:56:09 IST 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip -distributionSha256Sum=a8da5b02437a60819cad23e10fc7e9cf32bcb57029d9cb277e26eeff76ce014b +zipStoreBase=GRADLE_USER_HOME +distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302 diff --git a/libs/Android.bp b/libs/Android.bp index 2fa04ce0..15a9fcf2 100644 --- a/libs/Android.bp +++ b/libs/Android.bp @@ -1,5 +1,5 @@ java_import { name: "seedvault-lib-kotlin-bip39", - jars: ["kotlin-bip39-1.0.2.jar"], + jars: ["kotlin-bip39-jvm-1.0.4.jar"], sdk_version: "current", } diff --git a/libs/koin-android/Android.bp b/libs/koin-android/Android.bp index 1df86bba..c3369d9c 100644 --- a/libs/koin-android/Android.bp +++ b/libs/koin-android/Android.bp @@ -1,11 +1,11 @@ android_library_import { name: "seedvault-lib-koin-android", - aars: ["koin-android-3.1.2.aar"], + aars: ["koin-android-3.2.0.aar"], sdk_version: "current", } java_import { name: "seedvault-lib-koin-core-jvm", - jars: ["koin-core-jvm-3.1.2.jar"], + jars: ["koin-core-jvm-3.2.0.jar"], sdk_version: "current", } diff --git a/libs/koin-android/koin-android-3.1.2.aar b/libs/koin-android/koin-android-3.1.2.aar deleted file mode 100644 index be74b755..00000000 Binary files a/libs/koin-android/koin-android-3.1.2.aar and /dev/null differ diff --git a/libs/koin-android/koin-android-3.2.0.aar b/libs/koin-android/koin-android-3.2.0.aar new file mode 100644 index 00000000..3078638b Binary files /dev/null and b/libs/koin-android/koin-android-3.2.0.aar differ diff --git a/libs/koin-android/koin-core-jvm-3.1.2.jar b/libs/koin-android/koin-core-jvm-3.1.2.jar deleted file mode 100644 index 0efd1737..00000000 Binary files a/libs/koin-android/koin-core-jvm-3.1.2.jar and /dev/null differ diff --git a/libs/koin-android/koin-core-jvm-3.2.0.jar b/libs/koin-android/koin-core-jvm-3.2.0.jar new file mode 100644 index 00000000..912fc35f Binary files /dev/null and b/libs/koin-android/koin-core-jvm-3.2.0.jar differ diff --git a/libs/kotlin-bip39-1.0.2.jar b/libs/kotlin-bip39-1.0.2.jar deleted file mode 100644 index c05e1e05..00000000 Binary files a/libs/kotlin-bip39-1.0.2.jar and /dev/null differ diff --git a/libs/kotlin-bip39-jvm-1.0.4.jar b/libs/kotlin-bip39-jvm-1.0.4.jar new file mode 100644 index 00000000..55ff7745 Binary files /dev/null and b/libs/kotlin-bip39-jvm-1.0.4.jar differ diff --git a/storage/demo/build.gradle b/storage/demo/build.gradle index 6950ebc8..c99f2060 100644 --- a/storage/demo/build.gradle +++ b/storage/demo/build.gradle @@ -27,11 +27,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_11.toString() freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } lintOptions { diff --git a/storage/demo/src/main/AndroidManifest.xml b/storage/demo/src/main/AndroidManifest.xml index 43a2b25c..15b03a47 100644 --- a/storage/demo/src/main/AndroidManifest.xml +++ b/storage/demo/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - if (grantedMap[WRITE_EXTERNAL_STORAGE] == true) { - Toast.makeText(requireContext(), "Please try again now!", LENGTH_SHORT).show() - } - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -62,19 +50,19 @@ open class LogFragment : Fragment() { progressBar = v.findViewById(R.id.progressBar) horizontalProgressBar = v.findViewById(R.id.horizontalProgressBar) button = v.findViewById(R.id.button) - viewModel.backupLog.observe(viewLifecycleOwner, { progress -> + viewModel.backupLog.observe(viewLifecycleOwner) { progress -> progress.text?.let { adapter.addItem(it) } horizontalProgressBar.max = progress.total horizontalProgressBar.setProgress(progress.current, true) list.postDelayed({ list.scrollToPosition(adapter.itemCount - 1) }, 50) - }) - viewModel.backupButtonEnabled.observe(viewLifecycleOwner, { enabled -> + } + viewModel.backupButtonEnabled.observe(viewLifecycleOwner) { enabled -> button.isEnabled = enabled progressBar.visibility = if (enabled) INVISIBLE else VISIBLE if (!enabled) adapter.clear() - }) + } button.setOnClickListener { if (!checkPermission()) return@setOnClickListener viewModel.simulateBackup() @@ -120,23 +108,13 @@ open class LogFragment : Fragment() { } private fun checkPermission(): Boolean { - return if (Build.VERSION.SDK_INT >= 30) { - if (Environment.isExternalStorageManager()) return true - Toast.makeText(requireContext(), "Permission needed", LENGTH_SHORT).show() - val i = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { - data = Uri.parse("package:${requireContext().packageName}") - } - startActivity(i) - false - } else { - if (requireContext().checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { - true - } else { - Toast.makeText(requireContext(), "No storage permission", LENGTH_SHORT).show() - permissionRequest.launch(arrayOf(WRITE_EXTERNAL_STORAGE, ACCESS_MEDIA_LOCATION)) - false - } + if (Environment.isExternalStorageManager()) return true + Toast.makeText(requireContext(), "Permission needed", LENGTH_SHORT).show() + val i = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { + data = Uri.parse("package:${requireContext().packageName}") } + startActivity(i) + return false } } diff --git a/storage/demo/src/main/java/de/grobox/storagebackuptester/settings/InfoFragment.kt b/storage/demo/src/main/java/de/grobox/storagebackuptester/settings/InfoFragment.kt index 272c1d0b..2d090f46 100644 --- a/storage/demo/src/main/java/de/grobox/storagebackuptester/settings/InfoFragment.kt +++ b/storage/demo/src/main/java/de/grobox/storagebackuptester/settings/InfoFragment.kt @@ -1,6 +1,5 @@ package de.grobox.storagebackuptester.settings -import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.provider.DocumentsContract import android.provider.MediaStore @@ -39,16 +38,14 @@ class InfoFragment : MediaScanFragment() { } catch (e: IllegalArgumentException) { e.toString() } - val gen = if (SDK_INT >= 30) try { + val gen = try { MediaStore.getGeneration(context, volumeName) } catch (e: IllegalArgumentException) { e.toString() - } else null + } sb.appendLine(" $volumeName") sb.appendLine(" version: $version") - if (gen != null) { - sb.appendLine(" generation: $gen") - } + sb.appendLine(" generation: $gen") } sb.appendLine() sb.appendLine("Media files smaller than 100 KB: ${mediaFilesSmallerThan(100 * 1024)}") diff --git a/storage/lib/Android.bp b/storage/lib/Android.bp index 88113b1d..4d25e3d2 100644 --- a/storage/lib/Android.bp +++ b/storage/lib/Android.bp @@ -17,11 +17,15 @@ android_library { "seedvault-lib-tink-android", "libprotobuf-java-lite", "androidx.core_core-ktx", + "androidx.fragment_fragment-ktx", + "androidx.activity_activity-ktx", "androidx.documentfile_documentfile", "androidx.lifecycle_lifecycle-viewmodel-ktx", "androidx.room_room-runtime", "androidx-constraintlayout_constraintlayout", "com.google.android.material_material", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", ], plugins: [ "androidx.room_room-compiler-plugin", @@ -37,6 +41,6 @@ android_library { java_import { name: "seedvault-lib-tink-android", - jars: ["libs/tink-android-1.6.1.jar"], + jars: ["libs/tink-android-1.7.0.jar"], sdk_version: "current", } diff --git a/storage/lib/build.gradle b/storage/lib/build.gradle index e9544b3d..d820a205 100644 --- a/storage/lib/build.gradle +++ b/storage/lib/build.gradle @@ -4,7 +4,7 @@ plugins { id 'kotlin-android' id 'kotlin-kapt' id "org.jlleitschuh.gradle.ktlint" version "10.2.0" - id 'org.jetbrains.dokka' version '1.4.30' + id 'org.jetbrains.dokka' version "$kotlin_version" } android { @@ -28,12 +28,12 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' - languageVersion = "1.4" + jvmTarget = JavaVersion.VERSION_11.toString() + languageVersion = "1.6" freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' freeCompilerArgs += '-Xexplicit-api=strict' } diff --git a/storage/lib/libs/tink-android-1.6.1.jar b/storage/lib/libs/tink-android-1.6.1.jar deleted file mode 100644 index 4929a0c7..00000000 Binary files a/storage/lib/libs/tink-android-1.6.1.jar and /dev/null differ diff --git a/storage/lib/libs/tink-android-1.7.0.jar b/storage/lib/libs/tink-android-1.7.0.jar new file mode 100644 index 00000000..5bb1c7d8 Binary files /dev/null and b/storage/lib/libs/tink-android-1.7.0.jar differ diff --git a/storage/lib/src/main/AndroidManifest.xml b/storage/lib/src/main/AndroidManifest.xml index 08d696be..593723ef 100644 --- a/storage/lib/src/main/AndroidManifest.xml +++ b/storage/lib/src/main/AndroidManifest.xml @@ -3,13 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" package="org.calyxos.backup.storage"> - - - diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Backup.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Backup.kt index 48c74c2e..14dd470f 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Backup.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Backup.kt @@ -111,11 +111,12 @@ internal class Backup( } Log.e(TAG, "Changed files backup took $duration") } finally { - backupObserver?.onBackupComplete(duration?.toLongMilliseconds()) + backupObserver?.onBackupComplete(duration?.inWholeMilliseconds) } } @Throws(IOException::class, GeneralSecurityException::class) + @OptIn(ExperimentalTime::class) private suspend fun backupFiles( filesResult: FileScannerResult, availableChunkIds: HashSet, diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ChunksCacheRepopulater.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ChunksCacheRepopulater.kt index 764e994f..0b38d48e 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ChunksCacheRepopulater.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ChunksCacheRepopulater.kt @@ -8,7 +8,7 @@ import org.calyxos.backup.storage.measure import org.calyxos.backup.storage.plugin.SnapshotRetriever import java.io.IOException import java.security.GeneralSecurityException -import java.util.concurrent.TimeUnit.MILLISECONDS +import kotlin.time.DurationUnit.MILLISECONDS import kotlin.time.ExperimentalTime import kotlin.time.toDuration diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt index 6c143315..090ad385 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt @@ -18,6 +18,7 @@ import org.calyxos.backup.storage.plugin.saf.DocumentFileExt.listFilesBlocking import java.io.IOException import java.io.InputStream import java.io.OutputStream +import kotlin.time.ExperimentalTime private val folderRegex = Regex("^[a-f0-9]{16}\\.sv$") private val chunkFolderRegex = Regex("[a-f0-9]{2}") @@ -89,6 +90,7 @@ public abstract class SafStoragePlugin( * Chunk folders will get cached in the given [chunkFolders] for faster access. */ @Throws(IOException::class) + @OptIn(ExperimentalTime::class) private suspend fun populateChunkFolders( folder: DocumentFile, chunkFolders: HashMap, @@ -126,6 +128,7 @@ public abstract class SafStoragePlugin( } @Throws(IOException::class) + @OptIn(ExperimentalTime::class) private fun createMissingChunkFolders( root: DocumentFile, chunkFolders: HashMap, diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/prune/Pruner.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/prune/Pruner.kt index 17350158..3e503e86 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/prune/Pruner.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/prune/Pruner.kt @@ -47,7 +47,7 @@ internal class Pruner( } } Log.i(TAG, "Pruning took $duration") - backupObserver?.onPruneComplete(duration.toLongMilliseconds()) + backupObserver?.onPruneComplete(duration.inWholeMilliseconds) } @Throws(IOException::class, GeneralSecurityException::class) diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt index 01decb79..e25caf55 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt @@ -4,7 +4,6 @@ import android.content.ContentValues import android.content.Context import android.media.MediaScannerConnection import android.net.Uri -import android.os.Build.VERSION.SDK_INT import android.os.Environment.getExternalStorageDirectory import android.provider.MediaStore.MediaColumns import android.util.Log @@ -39,13 +38,7 @@ internal class FileRestore( val finalTag: String when { file.mediaFile != null -> { - bytes = if (SDK_INT < 30) { - // MediaProvider on API 29 doesn't let us write files into any folders freely, - // so don't attempt to restore via MediaStore API - restoreFile(file, streamWriter) - } else { - restoreFile(file.mediaFile, streamWriter) - } + bytes = restoreFile(file.mediaFile, streamWriter) finalTag = "M$tag" } file.docFile != null -> { @@ -112,10 +105,7 @@ internal class FileRestore( // changing owner requires backup permission put(MediaColumns.OWNER_PACKAGE_NAME, mediaFile.ownerPackageName) put(MediaColumns.IS_PENDING, 1) - if (SDK_INT >= 30) { - val isFavorite = if (mediaFile.isFavorite) 1 else 0 - put(MediaColumns.IS_FAVORITE, isFavorite) - } + put(MediaColumns.IS_FAVORITE, if (mediaFile.isFavorite) 1 else 0) } val contentUri = MediaType.fromBackupMediaType(mediaFile.type).contentUri val uri = contentResolver.insert(contentUri, contentValues)!! diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/Restore.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/Restore.kt index 297c1d5c..b338ac6d 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/Restore.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/Restore.kt @@ -52,6 +52,7 @@ internal class Restore( MultiChunkRestore(context, storagePlugin, fileRestore, streamCrypto, streamKey) } + @OptIn(ExperimentalTime::class) fun getBackupSnapshots(): Flow = flow { val numSnapshots: Int val time = measure { @@ -138,7 +139,7 @@ internal class Restore( Log.e(TAG, "Restoring ${split.multiChunkFiles.size} multi chunks took $multiChunkDuration.") val totalDuration = smallFilesDuration + singleChunkDuration + multiChunkDuration - observer?.onRestoreComplete(totalDuration.toLongMilliseconds()) + observer?.onRestoreComplete(totalDuration.inWholeMilliseconds) Log.e(TAG, "Restored $restoredFiles/$filesTotal files.") } diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/FileScanner.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/FileScanner.kt index bbce61c4..02d9cbdb 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/FileScanner.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/FileScanner.kt @@ -11,6 +11,7 @@ import org.calyxos.backup.storage.content.DocFile import org.calyxos.backup.storage.content.MediaFile import org.calyxos.backup.storage.db.UriStore import org.calyxos.backup.storage.measure +import kotlin.time.ExperimentalTime internal class FileScannerResult( val smallFiles: List, @@ -30,6 +31,7 @@ internal class FileScanner( private const val FILES_LARGE = "large" } + @OptIn(ExperimentalTime::class) fun getFiles(): FileScannerResult { // scan both APIs val mediaFiles = ArrayList() diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/MediaScanner.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/MediaScanner.kt index 673175e4..725e0a9d 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/MediaScanner.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/MediaScanner.kt @@ -5,13 +5,11 @@ import android.content.ContentUris import android.content.Context import android.database.Cursor import android.net.Uri -import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.provider.MediaStore.MediaColumns.IS_DOWNLOAD import android.util.Log -import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull import androidx.core.database.getLongOrNull import androidx.core.database.getStringOrNull @@ -23,7 +21,7 @@ import java.io.File public class MediaScanner(context: Context) { private companion object { - private val PROJECTION_29 = arrayOf( + private val PROJECTION = arrayOf( MediaStore.MediaColumns._ID, MediaStore.MediaColumns.RELATIVE_PATH, MediaStore.MediaColumns.DISPLAY_NAME, @@ -31,10 +29,6 @@ public class MediaScanner(context: Context) { MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.OWNER_PACKAGE_NAME, MediaStore.MediaColumns.VOLUME_NAME, - ) - - @RequiresApi(30) - private val PROJECTION_30 = arrayOf( MediaStore.MediaColumns.IS_FAVORITE, MediaStore.MediaColumns.GENERATION_MODIFIED, ) @@ -59,7 +53,7 @@ public class MediaScanner(context: Context) { internal fun scanMediaUri(uri: Uri, extraQuery: String? = null): List { val extras = Bundle().apply { val query = StringBuilder() - if (SDK_INT >= 30 && uri != MediaType.Downloads.contentUri) { + if (uri != MediaType.Downloads.contentUri) { query.append("$IS_DOWNLOAD=0") } extraQuery?.let { @@ -68,8 +62,7 @@ public class MediaScanner(context: Context) { } if (query.isNotEmpty()) putString(QUERY_ARG_SQL_SELECTION, query.toString()) } - val projection = if (SDK_INT >= 30) PROJECTION_29 + PROJECTION_30 else PROJECTION_29 - val cursor = contentResolver.query(uri, projection, extras, null) + val cursor = contentResolver.query(uri, PROJECTION, extras, null) return ArrayList(cursor?.count ?: 0).apply { cursor?.use { c -> while (c.moveToNext()) add(createMediaFile(c, uri)) @@ -94,13 +87,9 @@ public class MediaScanner(context: Context) { dir = cursor.getString(PROJECTION_PATH), fileName = cursor.getString(PROJECTION_NAME), dateModified = cursor.getLongOrNull(PROJECTION_DATE_MODIFIED), - generationModified = if (SDK_INT >= 30) cursor.getLongOrNull( - PROJECTION_GENERATION_MODIFIED - ) else null, + generationModified = cursor.getLongOrNull(PROJECTION_GENERATION_MODIFIED), size = cursor.getLong(PROJECTION_SIZE), - isFavorite = if (SDK_INT >= 30) { - cursor.getIntOrNull(PROJECTION_IS_FAVORITE) == 1 - } else false, + isFavorite = cursor.getIntOrNull(PROJECTION_IS_FAVORITE) == 1, ownerPackageName = cursor.getStringOrNull(PROJECTION_OWNER_PACKAGE_NAME), volume = cursor.getString(PROJECTION_VOLUME_NAME) ) diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/ui/Notifications.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/ui/Notifications.kt index 7f979eb5..b1828bec 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/ui/Notifications.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/ui/Notifications.kt @@ -7,7 +7,6 @@ import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_LOW import android.app.PendingIntent import android.content.Context -import android.os.Build.VERSION.SDK_INT import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat @@ -145,7 +144,7 @@ internal class Notifications(private val context: Context) { setShowWhen(false) setWhen(System.currentTimeMillis()) setProgress(expected, transferred, expected == 0) - if (SDK_INT >= 31) setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE) + setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE) }.build() }