Merge branch 'android13' into patch-2
This commit is contained in:
commit
f54b5448d7
65 changed files with 309 additions and 259 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Instrumentation tests: app" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests" singleton="true">
|
<configuration default="false" name="Instrumentation tests: app" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests" singleton="true">
|
||||||
<module name="seedvault.app" />
|
<module name="seedvault.app.androidTest" />
|
||||||
<option name="TESTING_TYPE" value="0" />
|
<option name="TESTING_TYPE" value="0" />
|
||||||
<option name="METHOD_NAME" value="" />
|
<option name="METHOD_NAME" value="" />
|
||||||
<option name="CLASS_NAME" value="" />
|
<option name="CLASS_NAME" value="" />
|
||||||
|
|
@ -8,10 +8,12 @@
|
||||||
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
|
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
|
||||||
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
|
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
|
||||||
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
|
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
|
||||||
|
<option name="RETENTION_ENABLED" value="No" />
|
||||||
|
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
|
||||||
|
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
|
||||||
<option name="CLEAR_LOGCAT" value="false" />
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
|
||||||
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
|
||||||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
<option name="DEBUGGER_TYPE" value="Auto" />
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
<Auto>
|
<Auto>
|
||||||
|
|
@ -40,7 +42,7 @@
|
||||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
|
||||||
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
</Profilers>
|
</Profilers>
|
||||||
|
|
@ -48,4 +50,4 @@
|
||||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
35
Android.bp
35
Android.bp
|
|
@ -26,12 +26,16 @@ android_app {
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"kotlin-stdlib-jdk8",
|
"kotlin-stdlib-jdk8",
|
||||||
"androidx.core_core-ktx",
|
"androidx.core_core-ktx",
|
||||||
|
"androidx.fragment_fragment-ktx",
|
||||||
|
"androidx.activity_activity-ktx",
|
||||||
"androidx.preference_preference",
|
"androidx.preference_preference",
|
||||||
"androidx.documentfile_documentfile",
|
"androidx.documentfile_documentfile",
|
||||||
"androidx.lifecycle_lifecycle-viewmodel-ktx",
|
"androidx.lifecycle_lifecycle-viewmodel-ktx",
|
||||||
"androidx.lifecycle_lifecycle-livedata-ktx",
|
"androidx.lifecycle_lifecycle-livedata-ktx",
|
||||||
"androidx-constraintlayout_constraintlayout",
|
"androidx-constraintlayout_constraintlayout",
|
||||||
"com.google.android.material_material",
|
"com.google.android.material_material",
|
||||||
|
"kotlinx-coroutines-android",
|
||||||
|
"kotlinx-coroutines-core",
|
||||||
// storage backup lib
|
// storage backup lib
|
||||||
"seedvault-lib-storage",
|
"seedvault-lib-storage",
|
||||||
// koin
|
// koin
|
||||||
|
|
@ -48,8 +52,9 @@ android_app {
|
||||||
privileged: true,
|
privileged: true,
|
||||||
required: [
|
required: [
|
||||||
"LocalContactsBackup",
|
"LocalContactsBackup",
|
||||||
"privapp_whitelist_com.stevesoltys.backup",
|
"com.stevesoltys.backup_allowlist",
|
||||||
"com.stevesoltys.backup_whitelist"
|
"com.stevesoltys.backup_default-permissions",
|
||||||
|
"com.stevesoltys.backup_privapp_allowlist"
|
||||||
],
|
],
|
||||||
optimize: {
|
optimize: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
@ -57,17 +62,25 @@ android_app {
|
||||||
}
|
}
|
||||||
|
|
||||||
prebuilt_etc {
|
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,
|
system_ext_specific: true,
|
||||||
sub_dir: "permissions",
|
sub_dir: "permissions",
|
||||||
src: "permissions_com.stevesoltys.seedvault.xml",
|
src: "permissions_com.stevesoltys.seedvault.xml",
|
||||||
filename_from_src: true,
|
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,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
## [12-3.0] - 2021-10-13
|
||||||
* Initial release for Android 12
|
* Initial release for Android 12
|
||||||
* Use the same (faster and more secure) crypto that storage backups use,
|
* Use the same (faster and more secure) crypto that storage backups use,
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -19,10 +19,19 @@ If you are having an issue/question, please look at our [FAQ](../../wiki/FAQ).
|
||||||
|
|
||||||
## Requirements
|
## 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).
|
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
|
## Getting Started
|
||||||
- Check out [the wiki](https://github.com/seedvault-app/seedvault/wiki) for information on building the application with
|
- Check out [the wiki](https://github.com/seedvault-app/seedvault/wiki) for information on building the application with
|
||||||
AOSP.
|
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.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.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.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
|
## Contributing
|
||||||
Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault.
|
Bug reports and pull requests are welcome on GitHub at https://github.com/seedvault-app/seedvault.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ android {
|
||||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 29 // leave at 29 for robolectric tests
|
minSdkVersion 32 // leave at 32 for robolectric tests
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionNameSuffix "-$gitDescribe"
|
versionNameSuffix "-$gitDescribe"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
@ -38,12 +38,12 @@ android {
|
||||||
abortOnError true
|
abortOnError true
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
targetCompatibility 1.8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
sourceCompatibility 1.8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
languageVersion = "1.4"
|
languageVersion = "1.6"
|
||||||
}
|
}
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests.all {
|
unitTests.all {
|
||||||
|
|
@ -122,13 +122,13 @@ dependencies {
|
||||||
* You can copy these libraries from ~/.gradle/caches/modules-2/files-2.1
|
* You can copy these libraries from ~/.gradle/caches/modules-2/files-2.1
|
||||||
*/
|
*/
|
||||||
// later versions than 2.1.1 require newer kotlin version
|
// 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-core-jvm:3.2.0"
|
||||||
// implementation "io.insert-koin:koin-android:3.1.2"
|
// implementation "io.insert-koin:koin-android:3.2.0"
|
||||||
implementation fileTree(include: ['*.jar'], dir: "${rootProject.rootDir}/libs/koin-android")
|
implementation fileTree(include: ['*.jar'], dir: "${rootProject.rootDir}/libs/koin-android")
|
||||||
implementation fileTree(include: ['*.aar'], 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 "cash.z.ecc.android:kotlin-bip39:1.0.4"
|
||||||
implementation fileTree(include: ['kotlin-bip39-1.0.2.jar'], dir: "${rootProject.rootDir}/libs")
|
implementation fileTree(include: ['kotlin-bip39-jvm-1.0.4.jar'], dir: "${rootProject.rootDir}/libs")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Dependencies (do not concern the AOSP build)
|
* Test Dependencies (do not concern the AOSP build)
|
||||||
|
|
@ -138,10 +138,11 @@ dependencies {
|
||||||
// anything less than 'implementation' fails tests run with gradlew
|
// anything less than 'implementation' fails tests run with gradlew
|
||||||
testImplementation rootProject.ext.aosp_libs
|
testImplementation rootProject.ext.aosp_libs
|
||||||
testImplementation 'androidx.test.ext:junit:1.1.3'
|
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
|
// https://github.com/robolectric/robolectric/issues/5245
|
||||||
exclude group: "com.google.auto.service", module: "auto-service"
|
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-api:$junit5_version"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$junit5_version"
|
testImplementation "org.junit.jupiter:junit-jupiter-params:$junit5_version"
|
||||||
testImplementation "io.mockk:mockk:$mockk_version"
|
testImplementation "io.mockk:mockk:$mockk_version"
|
||||||
|
|
@ -159,9 +160,6 @@ apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
tasks.withType(JavaCompile) {
|
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')
|
options.compilerArgs.add('-Xbootclasspath/p:app/libs/android.jar:app/libs/libcore.jar')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -7,13 +7,11 @@
|
||||||
<activity
|
<activity
|
||||||
android:name="com.stevesoltys.seedvault.settings.SettingsActivity"
|
android:name="com.stevesoltys.seedvault.settings.SettingsActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission=""
|
tools:remove="android:permission" />
|
||||||
tools:replace="android:permission" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.stevesoltys.seedvault.restore.RestoreActivity"
|
android:name="com.stevesoltys.seedvault.restore.RestoreActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission=""
|
tools:remove="android:permission" />
|
||||||
tools:replace="android:permission" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.stevesoltys.seedvault"
|
package="com.stevesoltys.seedvault"
|
||||||
android:versionCode="31000301"
|
android:versionCode="33000301"
|
||||||
android:versionName="12-3.0">
|
android:versionName="13-3.1">
|
||||||
<!--
|
<!--
|
||||||
The version code is the targeted SDK_VERSION plus 6 digits for our own version code.
|
The version code is the targeted SDK_VERSION plus 6 digits for our own version code.
|
||||||
The version name is the targeted Android version followed by - and our own version name.
|
The version name is the targeted Android version followed by - and our own version name.
|
||||||
|
|
@ -16,7 +16,11 @@
|
||||||
<!-- This is needed to check for internet access when backup is stored on network storage -->
|
<!-- This is needed to check for internet access when backup is stored on network storage -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<!-- This is needed to retrieve the available storage roots -->
|
<!-- This is needed to inform users about backup status and errors -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
|
<!-- (Optional) This is needed to retrieve the available storage roots.
|
||||||
|
The user needs to manually select a storage root, if not granted. -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_DOCUMENTS"
|
android:name="android.permission.MANAGE_DOCUMENTS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
@ -50,6 +54,16 @@
|
||||||
<!-- Used to authenticate saving a new recovery code -->
|
<!-- Used to authenticate saving a new recovery code -->
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
|
||||||
|
<!-- This is needed to query content providers in other users -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
<!-- Used to get logcat for system part of backup API, gets permission dialog -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_LOGS"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<!-- Permission used to open settings -->
|
<!-- Permission used to open settings -->
|
||||||
<permission
|
<permission
|
||||||
android:name="com.stevesoltys.seedvault.OPEN_SETTINGS"
|
android:name="com.stevesoltys.seedvault.OPEN_SETTINGS"
|
||||||
|
|
@ -60,11 +74,6 @@
|
||||||
android:name="com.stevesoltys.seedvault.RESTORE_BACKUP"
|
android:name="com.stevesoltys.seedvault.RESTORE_BACKUP"
|
||||||
android:protectionLevel="system|signature" />
|
android:protectionLevel="system|signature" />
|
||||||
|
|
||||||
<!-- This is needed to query content providers in other users -->
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
|
|
||||||
tools:ignore="ProtectedPermissions" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.stevesoltys.seedvault.restore.install
|
package com.stevesoltys.seedvault.restore.install
|
||||||
|
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
|
|
@ -81,10 +80,8 @@ internal class InstallProgressAdapter(
|
||||||
IN_PROGRESS -> {
|
IN_PROGRESS -> {
|
||||||
appStatus.visibility = INVISIBLE
|
appStatus.visibility = INVISIBLE
|
||||||
progressBar.visibility = VISIBLE
|
progressBar.visibility = VISIBLE
|
||||||
if (SDK_INT >= 30) {
|
progressBar.stateDescription =
|
||||||
progressBar.stateDescription =
|
context.getString(R.string.restore_app_status_installing)
|
||||||
context.getString(R.string.restore_app_status_installing)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SUCCEEDED -> {
|
SUCCEEDED -> {
|
||||||
appStatus.setImageResource(R.drawable.ic_check_green)
|
appStatus.setImageResource(R.drawable.ic_check_green)
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ internal class AppListRetriever(
|
||||||
time = time,
|
time = time,
|
||||||
status = status
|
status = status
|
||||||
)
|
)
|
||||||
}.sortedBy { it.name.toLowerCase(locale) }
|
}.sortedBy { it.name.lowercase(locale) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNotAllowedApps(): List<AppStatus> {
|
private fun getNotAllowedApps(): List<AppStatus> {
|
||||||
|
|
@ -120,7 +120,7 @@ internal class AppListRetriever(
|
||||||
time = 0,
|
time = 0,
|
||||||
status = FAILED_NOT_ALLOWED
|
status = FAILED_NOT_ALLOWED
|
||||||
)
|
)
|
||||||
}.sortedBy { it.name.toLowerCase(locale) }
|
}.sortedBy { it.name.lowercase(locale) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIcon(packageName: String): Drawable = when (packageName) {
|
private fun getIcon(packageName: String): Drawable = when (packageName) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,35 @@
|
||||||
package com.stevesoltys.seedvault.settings
|
package com.stevesoltys.seedvault.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||||
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.permitDiskReads
|
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() {
|
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?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
permitDiskReads {
|
permitDiskReads {
|
||||||
setPreferencesFromResource(R.xml.settings_expert, rootKey)
|
setPreferencesFromResource(R.xml.settings_expert, rootKey)
|
||||||
}
|
}
|
||||||
|
findPreference<Preference>("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() {
|
override fun onStart() {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.settings_backup_apk_dialog_disable) { dialog, _ ->
|
.setNegativeButton(R.string.settings_backup_apk_dialog_disable) { dialog, _ ->
|
||||||
apkBackup.isChecked = enable
|
apkBackup.isChecked = false
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
|
@ -130,14 +130,14 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
viewModel.lastBackupTime.observe(viewLifecycleOwner, { time ->
|
viewModel.lastBackupTime.observe(viewLifecycleOwner) { time ->
|
||||||
setAppBackupStatusSummary(time)
|
setAppBackupStatusSummary(time)
|
||||||
})
|
}
|
||||||
|
|
||||||
val backupFiles: Preference = findPreference("backup_files")!!
|
val backupFiles: Preference = findPreference("backup_files")!!
|
||||||
viewModel.filesSummary.observe(viewLifecycleOwner, { summary ->
|
viewModel.filesSummary.observe(viewLifecycleOwner) { summary ->
|
||||||
backupFiles.summary = summary
|
backupFiles.summary = summary
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
|
@ -160,10 +160,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
if (resources.getBoolean(R.bool.show_restore_in_settings)) {
|
if (resources.getBoolean(R.bool.show_restore_in_settings)) {
|
||||||
menuRestore?.isVisible = true
|
menuRestore?.isVisible = true
|
||||||
}
|
}
|
||||||
viewModel.backupPossible.observe(viewLifecycleOwner, { possible ->
|
viewModel.backupPossible.observe(viewLifecycleOwner) { possible ->
|
||||||
menuBackupNow?.isEnabled = possible
|
menuBackupNow?.isEnabled = possible
|
||||||
menuRestore?.isEnabled = possible
|
menuRestore?.isEnabled = possible
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Process.myUid
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
@ -35,8 +36,11 @@ import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.calyxos.backup.storage.api.StorageBackup
|
import org.calyxos.backup.storage.api.StorageBackup
|
||||||
import org.calyxos.backup.storage.backup.BackupJobService
|
import org.calyxos.backup.storage.backup.BackupJobService
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.Runtime.getRuntime
|
||||||
import java.util.concurrent.TimeUnit.HOURS
|
import java.util.concurrent.TimeUnit.HOURS
|
||||||
|
|
||||||
private const val TAG = "SettingsViewModel"
|
private const val TAG = "SettingsViewModel"
|
||||||
|
|
@ -193,9 +197,9 @@ internal class SettingsViewModel(
|
||||||
@UiThread
|
@UiThread
|
||||||
fun loadFilesSummary() = viewModelScope.launch {
|
fun loadFilesSummary() = viewModelScope.launch {
|
||||||
val uriSummary = storageBackup.getUriSummaryString()
|
val uriSummary = storageBackup.getUriSummaryString()
|
||||||
_filesSummary.value = if (uriSummary.isEmpty()) {
|
_filesSummary.value = uriSummary.ifEmpty {
|
||||||
app.getString(R.string.settings_backup_files_summary)
|
app.getString(R.string.settings_backup_files_summary)
|
||||||
} else uriSummary
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -233,4 +237,28 @@ internal class SettingsViewModel(
|
||||||
BackupJobService.cancelJob(app)
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,8 @@ internal class ApkBackup(
|
||||||
streamGetter: suspend (name: String) -> OutputStream,
|
streamGetter: suspend (name: String) -> OutputStream,
|
||||||
): List<ApkSplit> {
|
): List<ApkSplit> {
|
||||||
check(packageInfo.splitNames != null)
|
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) {
|
check(packageInfo.splitNames.size == splitSourceDirs.size) {
|
||||||
"Size Mismatch! ${packageInfo.splitNames.size} != ${splitSourceDirs.size} " +
|
"Size Mismatch! ${packageInfo.splitNames.size} != ${splitSourceDirs.size} " +
|
||||||
"splitNames is ${packageInfo.splitNames.toList()}, " +
|
"splitNames is ${packageInfo.splitNames.toList()}, " +
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,13 @@ internal class BackupCoordinator(
|
||||||
}
|
}
|
||||||
// hook in here to back up APKs of apps that are otherwise not allowed for backup
|
// hook in here to back up APKs of apps that are otherwise not allowed for backup
|
||||||
if (isPmBackup && settingsManager.canDoBackupNow()) {
|
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
|
result
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,7 @@ internal class KVBackup(
|
||||||
else state.db.close()
|
else state.db.close()
|
||||||
TRANSPORT_OK
|
TRANSPORT_OK
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Error uploading DB", e)
|
||||||
TRANSPORT_ERROR
|
TRANSPORT_ERROR
|
||||||
} finally {
|
} finally {
|
||||||
this.state = null
|
this.state = null
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.stevesoltys.seedvault.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
|
|
@ -40,12 +39,10 @@ internal abstract class AppViewHolder(protected val v: View) : RecyclerView.View
|
||||||
appInfo.visibility = GONE
|
appInfo.visibility = GONE
|
||||||
appStatus.visibility = INVISIBLE
|
appStatus.visibility = INVISIBLE
|
||||||
progressBar.visibility = VISIBLE
|
progressBar.visibility = VISIBLE
|
||||||
if (SDK_INT >= 30) {
|
progressBar.stateDescription = context.getString(
|
||||||
progressBar.stateDescription = context.getString(
|
if (isRestore) R.string.restore_restoring
|
||||||
if (isRestore) R.string.restore_restoring
|
else R.string.backup_app_in_progress
|
||||||
else R.string.backup_app_in_progress
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
appStatus.visibility = VISIBLE
|
appStatus.visibility = VISIBLE
|
||||||
progressBar.visibility = INVISIBLE
|
progressBar.visibility = INVISIBLE
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ internal class NotificationBackupObserver(
|
||||||
* @param currentBackupPackage The name of the package that now being backed up.
|
* @param currentBackupPackage The name of the package that now being backed up.
|
||||||
* @param backupProgress Current progress of backup for the package.
|
* @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)
|
showProgressNotification(currentBackupPackage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ internal class NotificationBackupObserver(
|
||||||
* that was initialized
|
* that was initialized
|
||||||
* @param status Zero on success; a nonzero error code if the backup operation failed.
|
* @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)) {
|
if (isLoggable(TAG, INFO)) {
|
||||||
Log.i(TAG, "Completed. Target: $target, status: $status")
|
Log.i(TAG, "Completed. Target: $target, status: $status")
|
||||||
}
|
}
|
||||||
|
|
@ -81,8 +81,8 @@ internal class NotificationBackupObserver(
|
||||||
nm.onBackupFinished(success, numBackedUp)
|
nm.onBackupFinished(success, numBackedUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showProgressNotification(packageName: String) {
|
private fun showProgressNotification(packageName: String?) {
|
||||||
if (currentPackage == packageName) return
|
if (packageName == null || currentPackage == packageName) return
|
||||||
|
|
||||||
if (isLoggable(TAG, INFO)) {
|
if (isLoggable(TAG, INFO)) {
|
||||||
"Showing progress notification for $currentPackage $numPackages/$expectedPackages".let {
|
"Showing progress notification for $currentPackage $numPackages/$expectedPackages".let {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager.LayoutParams.FLAG_SECURE
|
import android.view.WindowManager.LayoutParams.FLAG_SECURE
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.isDebugBuild
|
||||||
import com.stevesoltys.seedvault.ui.BackupActivity
|
import com.stevesoltys.seedvault.ui.BackupActivity
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
@ -15,7 +16,7 @@ class RecoveryCodeActivity : BackupActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
window.addFlags(FLAG_SECURE)
|
if (!isDebugBuild()) window.addFlags(FLAG_SECURE)
|
||||||
|
|
||||||
setContentView(R.layout.activity_recovery_code)
|
setContentView(R.layout.activity_recovery_code)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.content.Intent
|
||||||
import android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
import android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
import android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
import android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
import android.hardware.biometrics.BiometricPrompt
|
import android.hardware.biometrics.BiometricPrompt
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -22,7 +21,6 @@ import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat.getMainExecutor
|
import androidx.core.content.ContextCompat.getMainExecutor
|
||||||
|
|
@ -122,9 +120,9 @@ class RecoveryCodeInputFragment : Fragment() {
|
||||||
newCodeButton.visibility = if (forStoringNewCode) GONE else VISIBLE
|
newCodeButton.visibility = if (forStoringNewCode) GONE else VISIBLE
|
||||||
newCodeButton.setOnClickListener { generateNewCode() }
|
newCodeButton.setOnClickListener { generateNewCode() }
|
||||||
|
|
||||||
viewModel.existingCodeChecked.observeEvent(viewLifecycleOwner, { verified ->
|
viewModel.existingCodeChecked.observeEvent(viewLifecycleOwner) { verified ->
|
||||||
onExistingCodeChecked(verified)
|
onExistingCodeChecked(verified)
|
||||||
})
|
}
|
||||||
|
|
||||||
if (forStoringNewCode && isDebugBuild() && !viewModel.isRestore) debugPreFill()
|
if (forStoringNewCode && isDebugBuild() && !viewModel.isRestore) debugPreFill()
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +145,7 @@ class RecoveryCodeInputFragment : Fragment() {
|
||||||
}
|
}
|
||||||
if (forStoringNewCode) {
|
if (forStoringNewCode) {
|
||||||
val keyguardManager = requireContext().getSystemService(KeyguardManager::class.java)
|
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
|
// if we have a lock-screen secret, we can ask for it before storing the code
|
||||||
storeNewCodeAfterAuth(input)
|
storeNewCodeAfterAuth(input)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -159,7 +157,6 @@ class RecoveryCodeInputFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(30)
|
|
||||||
private fun storeNewCodeAfterAuth(input: List<CharSequence>) {
|
private fun storeNewCodeAfterAuth(input: List<CharSequence>) {
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
val biometricPrompt = BiometricPrompt.Builder(context)
|
val biometricPrompt = BiometricPrompt.Builder(context)
|
||||||
|
|
|
||||||
10
app/src/main/res/drawable/ic_bug_report.xml
Normal file
10
app/src/main/res/drawable/ic_bug_report.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -47,9 +47,11 @@
|
||||||
<string name="settings_expert_quota_title">Unlimited app quota</string>
|
<string name="settings_expert_quota_title">Unlimited app quota</string>
|
||||||
<string name="settings_expert_quota_summary">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.</string>
|
<string name="settings_expert_quota_summary">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.</string>
|
||||||
<string name="settings_expert_logcat_title">Save app log</string>
|
<string name="settings_expert_logcat_title">Save app log</string>
|
||||||
|
<string name="settings_expert_logcat_summary"></string>
|
||||||
|
<string name="settings_expert_logcat_error">Could not save app log</string>
|
||||||
<string name="settings_expert_logcat_summary">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!</string>
|
<string name="settings_expert_logcat_summary">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!</string>
|
||||||
<string name="settings_expert_logcat_error">Could not save app log</string>
|
<string name="settings_expert_logcat_error">Could not save app log</string>
|
||||||
|
|
||||||
<!-- Storage Location -->
|
<!-- Storage Location -->
|
||||||
<string name="storage_fragment_backup_title">Choose where to store backups</string>
|
<string name="storage_fragment_backup_title">Choose where to store backups</string>
|
||||||
<string name="storage_fragment_restore_title">Where to find your backups?</string>
|
<string name="storage_fragment_restore_title">Where to find your backups?</string>
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,9 @@
|
||||||
android:key="unlimited_quota"
|
android:key="unlimited_quota"
|
||||||
android:summary="@string/settings_expert_quota_summary"
|
android:summary="@string/settings_expert_quota_summary"
|
||||||
android:title="@string/settings_expert_quota_title" />
|
android:title="@string/settings_expert_quota_title" />
|
||||||
</PreferenceScreen>
|
<Preference
|
||||||
|
android:icon="@drawable/ic_bug_report"
|
||||||
|
android:key="logcat"
|
||||||
|
android:summary="@string/settings_expert_logcat_summary"
|
||||||
|
android:title="@string/settings_expert_logcat_title" />
|
||||||
|
</PreferenceScreen>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import kotlin.random.Random
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@Config(
|
@Config(
|
||||||
sdk = [29], // robolectric does not support 30, yet
|
sdk = [32], // robolectric does not support 33, yet
|
||||||
application = TestApp::class
|
application = TestApp::class
|
||||||
)
|
)
|
||||||
class MetadataManagerTest {
|
class MetadataManagerTest {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@Config(
|
@Config(
|
||||||
sdk = [29], // robolectric does not support 30, yet
|
sdk = [32], // robolectric does not support 33, yet
|
||||||
application = TestApp::class
|
application = TestApp::class
|
||||||
)
|
)
|
||||||
internal class DocumentFileTest {
|
internal class DocumentFileTest {
|
||||||
|
|
|
||||||
|
|
@ -133,14 +133,8 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
every { strictContext.cacheDir } returns tmpFile
|
every { strictContext.cacheDir } returns tmpFile
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
|
||||||
every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(capture(apkPath), any<Int>()) } returns packageInfo
|
||||||
every {
|
every { applicationInfo.loadIcon(pm) } returns icon
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
|
||||||
pm.loadItemIcon(
|
|
||||||
packageInfo.applicationInfo,
|
|
||||||
packageInfo.applicationInfo
|
|
||||||
)
|
|
||||||
} returns icon
|
|
||||||
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
||||||
every {
|
every {
|
||||||
splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName))
|
splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.stevesoltys.seedvault.restore.install
|
package com.stevesoltys.seedvault.restore.install
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
|
||||||
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
|
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
|
||||||
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
||||||
import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
|
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 { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
|
||||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||||
|
|
||||||
apkRestore.restore(backup).collectIndexed { i, value ->
|
apkRestore.restore(backup).collectIndexed { i, value ->
|
||||||
|
|
@ -176,14 +175,8 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
coEvery {
|
coEvery {
|
||||||
legacyStoragePlugin.getApkInputStream(token, packageName, "")
|
legacyStoragePlugin.getApkInputStream(token, packageName, "")
|
||||||
} returns apkInputStream
|
} returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
|
||||||
every {
|
every { applicationInfo.loadIcon(pm) } returns icon
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
|
||||||
pm.loadItemIcon(
|
|
||||||
packageInfo.applicationInfo,
|
|
||||||
packageInfo.applicationInfo
|
|
||||||
)
|
|
||||||
} returns icon
|
|
||||||
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
||||||
coEvery {
|
coEvery {
|
||||||
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
||||||
|
|
@ -200,13 +193,11 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val packageMetadata = this@ApkRestoreTest.packageMetadata.copy(system = true)
|
val packageMetadata = this@ApkRestoreTest.packageMetadata.copy(system = true)
|
||||||
packageMetadataMap[packageName] = packageMetadata
|
packageMetadataMap[packageName] = packageMetadata
|
||||||
packageInfo.applicationInfo = mockk()
|
|
||||||
val installedPackageInfo: PackageInfo = mockk()
|
val installedPackageInfo: PackageInfo = mockk()
|
||||||
val willFail = Random.nextBoolean()
|
val willFail = Random.nextBoolean()
|
||||||
val isSystemApp = Random.nextBoolean()
|
val isSystemApp = Random.nextBoolean()
|
||||||
|
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
every { packageInfo.applicationInfo.loadIcon(pm) } returns icon
|
|
||||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||||
|
|
||||||
if (willFail) {
|
if (willFail) {
|
||||||
|
|
@ -214,7 +205,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
pm.getPackageInfo(packageName, 0)
|
pm.getPackageInfo(packageName, 0)
|
||||||
} throws PackageManager.NameNotFoundException()
|
} throws PackageManager.NameNotFoundException()
|
||||||
} else {
|
} else {
|
||||||
installedPackageInfo.applicationInfo = ApplicationInfo().apply {
|
installedPackageInfo.applicationInfo = mockk {
|
||||||
flags =
|
flags =
|
||||||
if (!isSystemApp) FLAG_INSTALLED else FLAG_SYSTEM or FLAG_UPDATED_SYSTEM_APP
|
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 { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
|
||||||
every {
|
every { applicationInfo.loadIcon(pm) } returns icon
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
|
||||||
pm.loadItemIcon(
|
|
||||||
packageInfo.applicationInfo,
|
|
||||||
packageInfo.applicationInfo
|
|
||||||
)
|
|
||||||
} returns icon
|
|
||||||
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import kotlin.random.Random
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@Config(
|
@Config(
|
||||||
sdk = [29], // robolectric does not support 30, yet
|
sdk = [32], // robolectric does not support 33, yet
|
||||||
application = TestApp::class
|
application = TestApp::class
|
||||||
)
|
)
|
||||||
internal class DeviceInfoTest {
|
internal class DeviceInfoTest {
|
||||||
|
|
@ -62,12 +62,10 @@ internal class DeviceInfoTest {
|
||||||
assertFalse(deviceInfo.isSupportedLanguage("de"))
|
assertFalse(deviceInfo.isSupportedLanguage("de"))
|
||||||
|
|
||||||
// test areUnknownSplitsAllowed
|
// test areUnknownSplitsAllowed
|
||||||
val deviceName = "unknown robolectric"
|
assertTrue(deviceInfo.areUnknownSplitsAllowed("robolectric robolectric"))
|
||||||
if (onlyOnSameDevice) {
|
if (onlyOnSameDevice) {
|
||||||
assertTrue(deviceInfo.areUnknownSplitsAllowed(deviceName))
|
|
||||||
assertFalse(deviceInfo.areUnknownSplitsAllowed("foo bar"))
|
assertFalse(deviceInfo.areUnknownSplitsAllowed("foo bar"))
|
||||||
} else {
|
} else {
|
||||||
assertTrue(deviceInfo.areUnknownSplitsAllowed(deviceName))
|
|
||||||
assertTrue(deviceInfo.areUnknownSplitsAllowed("foo bar"))
|
assertTrue(deviceInfo.areUnknownSplitsAllowed("foo bar"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,13 @@ internal abstract class TransportTest {
|
||||||
|
|
||||||
protected val sigInfo: SigningInfo = mockk()
|
protected val sigInfo: SigningInfo = mockk()
|
||||||
protected val token = Random.nextLong()
|
protected val token = Random.nextLong()
|
||||||
|
protected val applicationInfo = mockk<ApplicationInfo> {
|
||||||
|
flags = FLAG_ALLOW_BACKUP or FLAG_INSTALLED
|
||||||
|
}
|
||||||
protected val packageInfo = PackageInfo().apply {
|
protected val packageInfo = PackageInfo().apply {
|
||||||
packageName = "org.example"
|
packageName = "org.example"
|
||||||
longVersionCode = Random.nextLong()
|
longVersionCode = Random.nextLong()
|
||||||
applicationInfo = ApplicationInfo().apply {
|
applicationInfo = this@TransportTest.applicationInfo
|
||||||
flags = FLAG_ALLOW_BACKUP or FLAG_INSTALLED
|
|
||||||
}
|
|
||||||
signingInfo = sigInfo
|
signingInfo = sigInfo
|
||||||
}
|
}
|
||||||
protected val pmPackageInfo = PackageInfo().apply {
|
protected val pmPackageInfo = PackageInfo().apply {
|
||||||
|
|
|
||||||
|
|
@ -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_OK
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
|
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
|
||||||
import android.content.pm.ApplicationInfo
|
|
||||||
import android.content.pm.ApplicationInfo.FLAG_STOPPED
|
import android.content.pm.ApplicationInfo.FLAG_STOPPED
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
@ -399,7 +398,9 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
PackageInfo().apply {
|
PackageInfo().apply {
|
||||||
packageName = "org.example.2"
|
packageName = "org.example.2"
|
||||||
// the second package does not get backed up, because it is stopped
|
// 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()
|
val packageMetadata: PackageMetadata = mockk()
|
||||||
|
|
|
||||||
18
build.gradle
18
build.gradle
|
|
@ -1,11 +1,11 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
// 1.3.21 Android 10
|
|
||||||
// 1.3.61 Android 11
|
// 1.3.61 Android 11
|
||||||
// 1.4.30 Android 12
|
// 1.4.30 Android 12
|
||||||
|
// 1.6.10 Android 13
|
||||||
// Check:
|
// Check:
|
||||||
// https://android.googlesource.com/platform/external/kotlinc/+/refs/tags/android-12.0.0_r2/build.txt
|
// https://android.googlesource.com/platform/external/kotlinc/+/refs/tags/android-13.0.0_r3/build.txt
|
||||||
ext.aosp_kotlin_version = '1.4.31' // 1.4.30 breaks Kotlin plugin in Android Studio
|
ext.aosp_kotlin_version = '1.6.10' // 1.6.10-release-923 in AOSP
|
||||||
ext.kotlin_version = '1.4.31'
|
ext.kotlin_version = '1.6.10'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
@ -15,15 +15,15 @@ buildscript {
|
||||||
//noinspection DifferentKotlinGradleVersion
|
//noinspection DifferentKotlinGradleVersion
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.17"
|
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 {
|
ext {
|
||||||
buildToolsVersion = '31.0.0'
|
buildToolsVersion = '33.0.0'
|
||||||
compileSdkVersion = 31
|
compileSdkVersion = 33
|
||||||
minSdkVersion = 29
|
minSdkVersion = 32
|
||||||
targetSdkVersion = 31
|
targetSdkVersion = 33
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: 'gradle/dependencies.gradle'
|
apply from: 'gradle/dependencies.gradle'
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = 1.8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -2,8 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.calyxos.backup.contacts"
|
package="org.calyxos.backup.contacts"
|
||||||
android:versionCode="31000301"
|
android:versionCode="33000301"
|
||||||
android:versionName="12-3.0">
|
android:versionName="13-3.1">
|
||||||
<!--
|
<!--
|
||||||
The version code is the targeted SDK_VERSION plus 6 digits for our own version code.
|
The version code is the targeted SDK_VERSION plus 6 digits for our own version code.
|
||||||
The version name is the targeted Android version followed by - and our own version name.
|
The version name is the targeted Android version followed by - and our own version name.
|
||||||
|
|
|
||||||
7
default-permissions_com.stevesoltys.seedvault.xml
Normal file
7
default-permissions_com.stevesoltys.seedvault.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
||||||
|
<exceptions>
|
||||||
|
<exception package="com.stevesoltys.seedvault">
|
||||||
|
<!-- Notifications -->
|
||||||
|
<permission name="android.permission.POST_NOTIFICATIONS" fixed="false"/>
|
||||||
|
</exception>
|
||||||
|
</exceptions>
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
ext {
|
ext {
|
||||||
// https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-12.0.0_r2/current/androidx/Android.bp#2943
|
// https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-13.0.0_r3/current/androidx/Android.bp#3901
|
||||||
ext.room_version = "2.3.0-beta02"
|
ext.room_version = "2.4.0-alpha05"
|
||||||
// https://android.googlesource.com/platform/external/protobuf/+/refs/tags/android-12.0.0_r2/java/pom.xml#7
|
// https://android.googlesource.com/platform/external/protobuf/+/refs/tags/android-13.0.0_r3/java/pom.xml#7
|
||||||
ext.protobuf_version = "3.9.1"
|
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"
|
junit4_version = "4.13.2"
|
||||||
junit5_version = "5.5.2" // careful, upgrading this can change a Cipher's IV size in tests!?
|
junit5_version = "5.7.2" // careful, upgrading this can change a Cipher's IV size in tests!?
|
||||||
mockk_version = "1.12.0"
|
mockk_version = "1.12.3"
|
||||||
espresso_version = "3.4.0"
|
espresso_version = "3.4.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,63 +39,63 @@ ext.kotlin_libs = [
|
||||||
],
|
],
|
||||||
coroutines: [
|
coroutines: [
|
||||||
dependencies.create('org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm') {
|
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
|
// https://android.googlesource.com/platform/external/kotlinx.coroutines/+/refs/tags/android-13.0.0_r3/CHANGES.md
|
||||||
version { strictly '1.4.2' }
|
version { strictly '1.5.2' }
|
||||||
},
|
},
|
||||||
dependencies.create('org.jetbrains.kotlinx:kotlinx-coroutines-android') {
|
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
|
// https://android.googlesource.com/platform/external/kotlinx.coroutines/+/refs/tags/android-13.0.0_r3/CHANGES.md
|
||||||
version { strictly '1.3.0' }
|
version { strictly '1.5.2' }
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.std_libs = [
|
ext.std_libs = [
|
||||||
androidx_core: [
|
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') {
|
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') {
|
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') {
|
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') {
|
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') {
|
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') {
|
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') {
|
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') {
|
androidx_constraintlayout: dependencies.create('androidx.constraintlayout:constraintlayout') {
|
||||||
version { strictly '2.0.0-beta7' }
|
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') {
|
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') {
|
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 = [
|
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 = [
|
ext.storage_libs = [
|
||||||
|
|
@ -103,7 +105,9 @@ ext.storage_libs = [
|
||||||
com_google_protobuf_javalite: dependencies.create('com.google.protobuf:protobuf-javalite') {
|
com_google_protobuf_javalite: dependencies.create('com.google.protobuf:protobuf-javalite') {
|
||||||
version { strictly "$protobuf_version" }
|
version { strictly "$protobuf_version" }
|
||||||
},
|
},
|
||||||
|
// https://github.com/google/tink/releases
|
||||||
com_google_crypto_tink_android: dependencies.create('com.google.crypto.tink:tink-android') {
|
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' }
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
8
gradle/wrapper/gradle-wrapper.properties
vendored
8
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
#Tue Aug 04 14:40:48 BRT 2020
|
#Fri Aug 19 10:56:09 IST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
distributionSha256Sum=a8da5b02437a60819cad23e10fc7e9cf32bcb57029d9cb277e26eeff76ce014b
|
distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
java_import {
|
java_import {
|
||||||
name: "seedvault-lib-kotlin-bip39",
|
name: "seedvault-lib-kotlin-bip39",
|
||||||
jars: ["kotlin-bip39-1.0.2.jar"],
|
jars: ["kotlin-bip39-jvm-1.0.4.jar"],
|
||||||
sdk_version: "current",
|
sdk_version: "current",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
android_library_import {
|
android_library_import {
|
||||||
name: "seedvault-lib-koin-android",
|
name: "seedvault-lib-koin-android",
|
||||||
aars: ["koin-android-3.1.2.aar"],
|
aars: ["koin-android-3.2.0.aar"],
|
||||||
sdk_version: "current",
|
sdk_version: "current",
|
||||||
}
|
}
|
||||||
|
|
||||||
java_import {
|
java_import {
|
||||||
name: "seedvault-lib-koin-core-jvm",
|
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",
|
sdk_version: "current",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
libs/koin-android/koin-android-3.2.0.aar
Normal file
BIN
libs/koin-android/koin-android-3.2.0.aar
Normal file
Binary file not shown.
Binary file not shown.
BIN
libs/koin-android/koin-core-jvm-3.2.0.jar
Normal file
BIN
libs/koin-android/koin-core-jvm-3.2.0.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
libs/kotlin-bip39-jvm-1.0.4.jar
Normal file
BIN
libs/kotlin-bip39-jvm-1.0.4.jar
Normal file
Binary file not shown.
|
|
@ -27,11 +27,11 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
|
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
|
||||||
}
|
}
|
||||||
lintOptions {
|
lintOptions {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.grobox.storagebackuptester">
|
package="de.grobox.storagebackuptester">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
package de.grobox.storagebackuptester
|
package de.grobox.storagebackuptester
|
||||||
|
|
||||||
import android.Manifest.permission.ACCESS_MEDIA_LOCATION
|
|
||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
|
import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
|
||||||
|
|
@ -21,7 +17,6 @@ import android.widget.Button
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_SHORT
|
import android.widget.Toast.LENGTH_SHORT
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
@ -43,13 +38,6 @@ open class LogFragment : Fragment() {
|
||||||
private lateinit var button: Button
|
private lateinit var button: Button
|
||||||
private val adapter = LogAdapter()
|
private val adapter = LogAdapter()
|
||||||
|
|
||||||
private val permissionRequest =
|
|
||||||
registerForActivityResult(RequestMultiplePermissions()) { grantedMap ->
|
|
||||||
if (grantedMap[WRITE_EXTERNAL_STORAGE] == true) {
|
|
||||||
Toast.makeText(requireContext(), "Please try again now!", LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -62,19 +50,19 @@ open class LogFragment : Fragment() {
|
||||||
progressBar = v.findViewById(R.id.progressBar)
|
progressBar = v.findViewById(R.id.progressBar)
|
||||||
horizontalProgressBar = v.findViewById(R.id.horizontalProgressBar)
|
horizontalProgressBar = v.findViewById(R.id.horizontalProgressBar)
|
||||||
button = v.findViewById(R.id.button)
|
button = v.findViewById(R.id.button)
|
||||||
viewModel.backupLog.observe(viewLifecycleOwner, { progress ->
|
viewModel.backupLog.observe(viewLifecycleOwner) { progress ->
|
||||||
progress.text?.let { adapter.addItem(it) }
|
progress.text?.let { adapter.addItem(it) }
|
||||||
horizontalProgressBar.max = progress.total
|
horizontalProgressBar.max = progress.total
|
||||||
horizontalProgressBar.setProgress(progress.current, true)
|
horizontalProgressBar.setProgress(progress.current, true)
|
||||||
list.postDelayed({
|
list.postDelayed({
|
||||||
list.scrollToPosition(adapter.itemCount - 1)
|
list.scrollToPosition(adapter.itemCount - 1)
|
||||||
}, 50)
|
}, 50)
|
||||||
})
|
}
|
||||||
viewModel.backupButtonEnabled.observe(viewLifecycleOwner, { enabled ->
|
viewModel.backupButtonEnabled.observe(viewLifecycleOwner) { enabled ->
|
||||||
button.isEnabled = enabled
|
button.isEnabled = enabled
|
||||||
progressBar.visibility = if (enabled) INVISIBLE else VISIBLE
|
progressBar.visibility = if (enabled) INVISIBLE else VISIBLE
|
||||||
if (!enabled) adapter.clear()
|
if (!enabled) adapter.clear()
|
||||||
})
|
}
|
||||||
button.setOnClickListener {
|
button.setOnClickListener {
|
||||||
if (!checkPermission()) return@setOnClickListener
|
if (!checkPermission()) return@setOnClickListener
|
||||||
viewModel.simulateBackup()
|
viewModel.simulateBackup()
|
||||||
|
|
@ -120,23 +108,13 @@ open class LogFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkPermission(): Boolean {
|
private fun checkPermission(): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= 30) {
|
if (Environment.isExternalStorageManager()) return true
|
||||||
if (Environment.isExternalStorageManager()) return true
|
Toast.makeText(requireContext(), "Permission needed", LENGTH_SHORT).show()
|
||||||
Toast.makeText(requireContext(), "Permission needed", LENGTH_SHORT).show()
|
val i = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
||||||
val i = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
data = Uri.parse("package:${requireContext().packageName}")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
startActivity(i)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package de.grobox.storagebackuptester.settings
|
package de.grobox.storagebackuptester.settings
|
||||||
|
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
|
@ -39,16 +38,14 @@ class InfoFragment : MediaScanFragment() {
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
e.toString()
|
e.toString()
|
||||||
}
|
}
|
||||||
val gen = if (SDK_INT >= 30) try {
|
val gen = try {
|
||||||
MediaStore.getGeneration(context, volumeName)
|
MediaStore.getGeneration(context, volumeName)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
e.toString()
|
e.toString()
|
||||||
} else null
|
}
|
||||||
sb.appendLine(" $volumeName")
|
sb.appendLine(" $volumeName")
|
||||||
sb.appendLine(" version: $version")
|
sb.appendLine(" version: $version")
|
||||||
if (gen != null) {
|
sb.appendLine(" generation: $gen")
|
||||||
sb.appendLine(" generation: $gen")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sb.appendLine()
|
sb.appendLine()
|
||||||
sb.appendLine("Media files smaller than 100 KB: ${mediaFilesSmallerThan(100 * 1024)}")
|
sb.appendLine("Media files smaller than 100 KB: ${mediaFilesSmallerThan(100 * 1024)}")
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,15 @@ android_library {
|
||||||
"seedvault-lib-tink-android",
|
"seedvault-lib-tink-android",
|
||||||
"libprotobuf-java-lite",
|
"libprotobuf-java-lite",
|
||||||
"androidx.core_core-ktx",
|
"androidx.core_core-ktx",
|
||||||
|
"androidx.fragment_fragment-ktx",
|
||||||
|
"androidx.activity_activity-ktx",
|
||||||
"androidx.documentfile_documentfile",
|
"androidx.documentfile_documentfile",
|
||||||
"androidx.lifecycle_lifecycle-viewmodel-ktx",
|
"androidx.lifecycle_lifecycle-viewmodel-ktx",
|
||||||
"androidx.room_room-runtime",
|
"androidx.room_room-runtime",
|
||||||
"androidx-constraintlayout_constraintlayout",
|
"androidx-constraintlayout_constraintlayout",
|
||||||
"com.google.android.material_material",
|
"com.google.android.material_material",
|
||||||
|
"kotlinx-coroutines-android",
|
||||||
|
"kotlinx-coroutines-core",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"androidx.room_room-compiler-plugin",
|
"androidx.room_room-compiler-plugin",
|
||||||
|
|
@ -37,6 +41,6 @@ android_library {
|
||||||
|
|
||||||
java_import {
|
java_import {
|
||||||
name: "seedvault-lib-tink-android",
|
name: "seedvault-lib-tink-android",
|
||||||
jars: ["libs/tink-android-1.6.1.jar"],
|
jars: ["libs/tink-android-1.7.0.jar"],
|
||||||
sdk_version: "current",
|
sdk_version: "current",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ plugins {
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
id "org.jlleitschuh.gradle.ktlint" version "10.2.0"
|
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 {
|
android {
|
||||||
|
|
@ -28,12 +28,12 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
languageVersion = "1.4"
|
languageVersion = "1.6"
|
||||||
freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
|
freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
|
||||||
freeCompilerArgs += '-Xexplicit-api=strict'
|
freeCompilerArgs += '-Xexplicit-api=strict'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
storage/lib/libs/tink-android-1.7.0.jar
Normal file
BIN
storage/lib/libs/tink-android-1.7.0.jar
Normal file
Binary file not shown.
|
|
@ -3,13 +3,6 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.calyxos.backup.storage">
|
package="org.calyxos.backup.storage">
|
||||||
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="29" />
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
||||||
android:maxSdkVersion="29" />
|
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
|
||||||
|
|
@ -111,11 +111,12 @@ internal class Backup(
|
||||||
}
|
}
|
||||||
Log.e(TAG, "Changed files backup took $duration")
|
Log.e(TAG, "Changed files backup took $duration")
|
||||||
} finally {
|
} finally {
|
||||||
backupObserver?.onBackupComplete(duration?.toLongMilliseconds())
|
backupObserver?.onBackupComplete(duration?.inWholeMilliseconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
private suspend fun backupFiles(
|
private suspend fun backupFiles(
|
||||||
filesResult: FileScannerResult,
|
filesResult: FileScannerResult,
|
||||||
availableChunkIds: HashSet<String>,
|
availableChunkIds: HashSet<String>,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import org.calyxos.backup.storage.measure
|
||||||
import org.calyxos.backup.storage.plugin.SnapshotRetriever
|
import org.calyxos.backup.storage.plugin.SnapshotRetriever
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.GeneralSecurityException
|
import java.security.GeneralSecurityException
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
import kotlin.time.DurationUnit.MILLISECONDS
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
import kotlin.time.toDuration
|
import kotlin.time.toDuration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import org.calyxos.backup.storage.plugin.saf.DocumentFileExt.listFilesBlocking
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
private val folderRegex = Regex("^[a-f0-9]{16}\\.sv$")
|
private val folderRegex = Regex("^[a-f0-9]{16}\\.sv$")
|
||||||
private val chunkFolderRegex = Regex("[a-f0-9]{2}")
|
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.
|
* Chunk folders will get cached in the given [chunkFolders] for faster access.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
private suspend fun populateChunkFolders(
|
private suspend fun populateChunkFolders(
|
||||||
folder: DocumentFile,
|
folder: DocumentFile,
|
||||||
chunkFolders: HashMap<String, DocumentFile>,
|
chunkFolders: HashMap<String, DocumentFile>,
|
||||||
|
|
@ -126,6 +128,7 @@ public abstract class SafStoragePlugin(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
private fun createMissingChunkFolders(
|
private fun createMissingChunkFolders(
|
||||||
root: DocumentFile,
|
root: DocumentFile,
|
||||||
chunkFolders: HashMap<String, DocumentFile>,
|
chunkFolders: HashMap<String, DocumentFile>,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ internal class Pruner(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Pruning took $duration")
|
Log.i(TAG, "Pruning took $duration")
|
||||||
backupObserver?.onPruneComplete(duration.toLongMilliseconds())
|
backupObserver?.onPruneComplete(duration.inWholeMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, GeneralSecurityException::class)
|
@Throws(IOException::class, GeneralSecurityException::class)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.os.Environment.getExternalStorageDirectory
|
import android.os.Environment.getExternalStorageDirectory
|
||||||
import android.provider.MediaStore.MediaColumns
|
import android.provider.MediaStore.MediaColumns
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -39,13 +38,7 @@ internal class FileRestore(
|
||||||
val finalTag: String
|
val finalTag: String
|
||||||
when {
|
when {
|
||||||
file.mediaFile != null -> {
|
file.mediaFile != null -> {
|
||||||
bytes = if (SDK_INT < 30) {
|
bytes = restoreFile(file.mediaFile, streamWriter)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
finalTag = "M$tag"
|
finalTag = "M$tag"
|
||||||
}
|
}
|
||||||
file.docFile != null -> {
|
file.docFile != null -> {
|
||||||
|
|
@ -112,10 +105,7 @@ internal class FileRestore(
|
||||||
// changing owner requires backup permission
|
// changing owner requires backup permission
|
||||||
put(MediaColumns.OWNER_PACKAGE_NAME, mediaFile.ownerPackageName)
|
put(MediaColumns.OWNER_PACKAGE_NAME, mediaFile.ownerPackageName)
|
||||||
put(MediaColumns.IS_PENDING, 1)
|
put(MediaColumns.IS_PENDING, 1)
|
||||||
if (SDK_INT >= 30) {
|
put(MediaColumns.IS_FAVORITE, if (mediaFile.isFavorite) 1 else 0)
|
||||||
val isFavorite = if (mediaFile.isFavorite) 1 else 0
|
|
||||||
put(MediaColumns.IS_FAVORITE, isFavorite)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val contentUri = MediaType.fromBackupMediaType(mediaFile.type).contentUri
|
val contentUri = MediaType.fromBackupMediaType(mediaFile.type).contentUri
|
||||||
val uri = contentResolver.insert(contentUri, contentValues)!!
|
val uri = contentResolver.insert(contentUri, contentValues)!!
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ internal class Restore(
|
||||||
MultiChunkRestore(context, storagePlugin, fileRestore, streamCrypto, streamKey)
|
MultiChunkRestore(context, storagePlugin, fileRestore, streamCrypto, streamKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
fun getBackupSnapshots(): Flow<SnapshotResult> = flow {
|
fun getBackupSnapshots(): Flow<SnapshotResult> = flow {
|
||||||
val numSnapshots: Int
|
val numSnapshots: Int
|
||||||
val time = measure {
|
val time = measure {
|
||||||
|
|
@ -138,7 +139,7 @@ internal class Restore(
|
||||||
Log.e(TAG, "Restoring ${split.multiChunkFiles.size} multi chunks took $multiChunkDuration.")
|
Log.e(TAG, "Restoring ${split.multiChunkFiles.size} multi chunks took $multiChunkDuration.")
|
||||||
|
|
||||||
val totalDuration = smallFilesDuration + singleChunkDuration + multiChunkDuration
|
val totalDuration = smallFilesDuration + singleChunkDuration + multiChunkDuration
|
||||||
observer?.onRestoreComplete(totalDuration.toLongMilliseconds())
|
observer?.onRestoreComplete(totalDuration.inWholeMilliseconds)
|
||||||
Log.e(TAG, "Restored $restoredFiles/$filesTotal files.")
|
Log.e(TAG, "Restored $restoredFiles/$filesTotal files.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import org.calyxos.backup.storage.content.DocFile
|
||||||
import org.calyxos.backup.storage.content.MediaFile
|
import org.calyxos.backup.storage.content.MediaFile
|
||||||
import org.calyxos.backup.storage.db.UriStore
|
import org.calyxos.backup.storage.db.UriStore
|
||||||
import org.calyxos.backup.storage.measure
|
import org.calyxos.backup.storage.measure
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
internal class FileScannerResult(
|
internal class FileScannerResult(
|
||||||
val smallFiles: List<ContentFile>,
|
val smallFiles: List<ContentFile>,
|
||||||
|
|
@ -30,6 +31,7 @@ internal class FileScanner(
|
||||||
private const val FILES_LARGE = "large"
|
private const val FILES_LARGE = "large"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
fun getFiles(): FileScannerResult {
|
fun getFiles(): FileScannerResult {
|
||||||
// scan both APIs
|
// scan both APIs
|
||||||
val mediaFiles = ArrayList<ContentFile>()
|
val mediaFiles = ArrayList<ContentFile>()
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,11 @@ import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.MediaColumns.IS_DOWNLOAD
|
import android.provider.MediaStore.MediaColumns.IS_DOWNLOAD
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.database.getIntOrNull
|
import androidx.core.database.getIntOrNull
|
||||||
import androidx.core.database.getLongOrNull
|
import androidx.core.database.getLongOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
|
|
@ -23,7 +21,7 @@ import java.io.File
|
||||||
public class MediaScanner(context: Context) {
|
public class MediaScanner(context: Context) {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private val PROJECTION_29 = arrayOf(
|
private val PROJECTION = arrayOf(
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
|
@ -31,10 +29,6 @@ public class MediaScanner(context: Context) {
|
||||||
MediaStore.MediaColumns.SIZE,
|
MediaStore.MediaColumns.SIZE,
|
||||||
MediaStore.MediaColumns.OWNER_PACKAGE_NAME,
|
MediaStore.MediaColumns.OWNER_PACKAGE_NAME,
|
||||||
MediaStore.MediaColumns.VOLUME_NAME,
|
MediaStore.MediaColumns.VOLUME_NAME,
|
||||||
)
|
|
||||||
|
|
||||||
@RequiresApi(30)
|
|
||||||
private val PROJECTION_30 = arrayOf(
|
|
||||||
MediaStore.MediaColumns.IS_FAVORITE,
|
MediaStore.MediaColumns.IS_FAVORITE,
|
||||||
MediaStore.MediaColumns.GENERATION_MODIFIED,
|
MediaStore.MediaColumns.GENERATION_MODIFIED,
|
||||||
)
|
)
|
||||||
|
|
@ -59,7 +53,7 @@ public class MediaScanner(context: Context) {
|
||||||
internal fun scanMediaUri(uri: Uri, extraQuery: String? = null): List<MediaFile> {
|
internal fun scanMediaUri(uri: Uri, extraQuery: String? = null): List<MediaFile> {
|
||||||
val extras = Bundle().apply {
|
val extras = Bundle().apply {
|
||||||
val query = StringBuilder()
|
val query = StringBuilder()
|
||||||
if (SDK_INT >= 30 && uri != MediaType.Downloads.contentUri) {
|
if (uri != MediaType.Downloads.contentUri) {
|
||||||
query.append("$IS_DOWNLOAD=0")
|
query.append("$IS_DOWNLOAD=0")
|
||||||
}
|
}
|
||||||
extraQuery?.let {
|
extraQuery?.let {
|
||||||
|
|
@ -68,8 +62,7 @@ public class MediaScanner(context: Context) {
|
||||||
}
|
}
|
||||||
if (query.isNotEmpty()) putString(QUERY_ARG_SQL_SELECTION, query.toString())
|
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<MediaFile>(cursor?.count ?: 0).apply {
|
return ArrayList<MediaFile>(cursor?.count ?: 0).apply {
|
||||||
cursor?.use { c ->
|
cursor?.use { c ->
|
||||||
while (c.moveToNext()) add(createMediaFile(c, uri))
|
while (c.moveToNext()) add(createMediaFile(c, uri))
|
||||||
|
|
@ -94,13 +87,9 @@ public class MediaScanner(context: Context) {
|
||||||
dir = cursor.getString(PROJECTION_PATH),
|
dir = cursor.getString(PROJECTION_PATH),
|
||||||
fileName = cursor.getString(PROJECTION_NAME),
|
fileName = cursor.getString(PROJECTION_NAME),
|
||||||
dateModified = cursor.getLongOrNull(PROJECTION_DATE_MODIFIED),
|
dateModified = cursor.getLongOrNull(PROJECTION_DATE_MODIFIED),
|
||||||
generationModified = if (SDK_INT >= 30) cursor.getLongOrNull(
|
generationModified = cursor.getLongOrNull(PROJECTION_GENERATION_MODIFIED),
|
||||||
PROJECTION_GENERATION_MODIFIED
|
|
||||||
) else null,
|
|
||||||
size = cursor.getLong(PROJECTION_SIZE),
|
size = cursor.getLong(PROJECTION_SIZE),
|
||||||
isFavorite = if (SDK_INT >= 30) {
|
isFavorite = cursor.getIntOrNull(PROJECTION_IS_FAVORITE) == 1,
|
||||||
cursor.getIntOrNull(PROJECTION_IS_FAVORITE) == 1
|
|
||||||
} else false,
|
|
||||||
ownerPackageName = cursor.getStringOrNull(PROJECTION_OWNER_PACKAGE_NAME),
|
ownerPackageName = cursor.getStringOrNull(PROJECTION_OWNER_PACKAGE_NAME),
|
||||||
volume = cursor.getString(PROJECTION_VOLUME_NAME)
|
volume = cursor.getString(PROJECTION_VOLUME_NAME)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.app.NotificationManager
|
||||||
import android.app.NotificationManager.IMPORTANCE_LOW
|
import android.app.NotificationManager.IMPORTANCE_LOW
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build.VERSION.SDK_INT
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
@ -145,7 +144,7 @@ internal class Notifications(private val context: Context) {
|
||||||
setShowWhen(false)
|
setShowWhen(false)
|
||||||
setWhen(System.currentTimeMillis())
|
setWhen(System.currentTimeMillis())
|
||||||
setProgress(expected, transferred, expected == 0)
|
setProgress(expected, transferred, expected == 0)
|
||||||
if (SDK_INT >= 31) setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)
|
setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue