From 5d38f061d908d802e341784de335f7ef48b9f51c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 20 Jan 2020 09:53:51 -0300 Subject: [PATCH 1/4] Make some app status list tweaks suggested by our designer Glenn --- .../seedvault/restore/InstallProgressAdapter.kt | 2 +- .../java/com/stevesoltys/seedvault/ui/AppViewHolder.kt | 8 ++------ app/src/main/res/drawable/ic_block_yellow.xml | 9 --------- app/src/main/res/drawable/ic_cancel_red.xml | 9 --------- .../drawable/{ic_error_yellow.xml => ic_error_red.xml} | 2 +- ...button_unchecked_yellow.xml => ic_warning_yellow.xml} | 2 +- app/src/main/res/layout/list_item_app_status.xml | 4 ++-- 7 files changed, 7 insertions(+), 29 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_block_yellow.xml delete mode 100644 app/src/main/res/drawable/ic_cancel_red.xml rename app/src/main/res/drawable/{ic_error_yellow.xml => ic_error_red.xml} (89%) rename app/src/main/res/drawable/{ic_radio_button_unchecked_yellow.xml => ic_warning_yellow.xml} (59%) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt index d1e59b8b..34ef126b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt @@ -56,7 +56,7 @@ internal class AppInstallViewHolder(v: View) : AppViewHolder(v) { progressBar.visibility = INVISIBLE } FAILED -> { - appStatus.setImageResource(R.drawable.ic_cancel_red) + appStatus.setImageResource(R.drawable.ic_error_red) appStatus.visibility = VISIBLE progressBar.visibility = INVISIBLE } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt index 120913ad..32ba6cf4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt @@ -43,13 +43,9 @@ internal open class AppViewHolder(v: View) : RecyclerView.ViewHolder(v) { appInfo.visibility = GONE when (status) { SUCCEEDED -> appStatus.setImageResource(R.drawable.ic_check_green) - FAILED -> appStatus.setImageResource(R.drawable.ic_cancel_red) + FAILED -> appStatus.setImageResource(R.drawable.ic_error_red) else -> { - when (status) { - FAILED_NO_DATA -> appStatus.setImageResource(R.drawable.ic_radio_button_unchecked_yellow) - FAILED_NOT_ALLOWED -> appStatus.setImageResource(R.drawable.ic_block_yellow) - else -> appStatus.setImageResource(R.drawable.ic_error_yellow) - } + appStatus.setImageResource(R.drawable.ic_warning_yellow) appInfo.text = status.getInfo() appInfo.visibility = VISIBLE } diff --git a/app/src/main/res/drawable/ic_block_yellow.xml b/app/src/main/res/drawable/ic_block_yellow.xml deleted file mode 100644 index 8da274f6..00000000 --- a/app/src/main/res/drawable/ic_block_yellow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_cancel_red.xml b/app/src/main/res/drawable/ic_cancel_red.xml deleted file mode 100644 index 2ced3613..00000000 --- a/app/src/main/res/drawable/ic_cancel_red.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_error_yellow.xml b/app/src/main/res/drawable/ic_error_red.xml similarity index 89% rename from app/src/main/res/drawable/ic_error_yellow.xml rename to app/src/main/res/drawable/ic_error_red.xml index a0fb86b6..ae907c98 100644 --- a/app/src/main/res/drawable/ic_error_yellow.xml +++ b/app/src/main/res/drawable/ic_error_red.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_radio_button_unchecked_yellow.xml b/app/src/main/res/drawable/ic_warning_yellow.xml similarity index 59% rename from app/src/main/res/drawable/ic_radio_button_unchecked_yellow.xml rename to app/src/main/res/drawable/ic_warning_yellow.xml index c416c2d1..1edf4358 100644 --- a/app/src/main/res/drawable/ic_radio_button_unchecked_yellow.xml +++ b/app/src/main/res/drawable/ic_warning_yellow.xml @@ -5,5 +5,5 @@ android:viewportHeight="24.0"> + android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z" /> diff --git a/app/src/main/res/layout/list_item_app_status.xml b/app/src/main/res/layout/list_item_app_status.xml index 6fd59700..e7a0236d 100644 --- a/app/src/main/res/layout/list_item_app_status.xml +++ b/app/src/main/res/layout/list_item_app_status.xml @@ -46,8 +46,8 @@ Date: Mon, 20 Jan 2020 10:01:44 -0300 Subject: [PATCH 2/4] Credit Glenn Sorrentino on About page --- .../seedvault/settings/AboutDialogFragment.kt | 1 + app/src/main/res/layout/fragment_about.xml | 17 +++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt index 8653777b..2008d444 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt @@ -23,6 +23,7 @@ class AboutDialogFragment : DialogFragment() { val linkMovementMethod = LinkMovementMethod.getInstance() licenseView.movementMethod = linkMovementMethod authorView.movementMethod = linkMovementMethod + designView.movementMethod = linkMovementMethod sponsorView.movementMethod = linkMovementMethod } diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 7b94c57c..d391f61c 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -88,6 +88,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/licenseView" /> + + + app:layout_constraintTop_toBottomOf="@+id/designView" /> A backup application using Android\'s internal backup API. License: Apache2 Written by: Steve Soltys and Torsten Grote + Design by: Glenn Sorrentino Sponsored by: Calyx Institute for use in CalyxOS Source Code: https://github.com/stevesoltys/seedvault From 324da2a9e91b9a96323ca4de1a7debd2131e434a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 20 Jan 2020 11:37:51 -0300 Subject: [PATCH 3/4] Allow the user to exclude apps from backup Closes #70 --- .../restore/RestoreProgressAdapter.kt | 2 +- .../seedvault/settings/AppStatusAdapter.kt | 55 +++++++++++++++++-- .../seedvault/settings/AppStatusFragment.kt | 38 ++++++++++++- .../seedvault/settings/SettingsManager.kt | 16 ++++++ .../seedvault/settings/SettingsViewModel.kt | 16 ++++++ .../transport/backup/BackupCoordinator.kt | 13 +++-- .../stevesoltys/seedvault/ui/AppViewHolder.kt | 11 +++- .../ui/RequireProvisioningViewModel.kt | 2 +- .../main/res/layout/fragment_app_status.xml | 3 - .../res/layout/fragment_restore_progress.xml | 3 +- .../main/res/layout/list_item_app_status.xml | 23 ++++++-- app/src/main/res/menu/app_status_menu.xml | 10 ++++ app/src/main/res/values/strings.xml | 1 + .../transport/backup/BackupCoordinatorTest.kt | 16 ++++++ 14 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/menu/app_status_menu.xml diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt index 310e4a6b..b22dd354 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt @@ -68,7 +68,7 @@ internal class RestoreProgressAdapter : Adapter() { } -internal enum class AppRestoreStatus { +enum class AppRestoreStatus { IN_PROGRESS, SUCCEEDED, FAILED, diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt index 76c7ca0a..291f88c6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt @@ -3,11 +3,14 @@ package com.stevesoltys.seedvault.settings import android.graphics.drawable.Drawable import android.view.LayoutInflater import android.view.View +import android.view.View.GONE +import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil.DiffResult import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.NO_POSITION import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.restore.AppRestoreStatus import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED @@ -15,9 +18,10 @@ import com.stevesoltys.seedvault.settings.AppStatusAdapter.AppStatusViewHolder import com.stevesoltys.seedvault.ui.AppViewHolder import com.stevesoltys.seedvault.ui.toRelativeTime -internal class AppStatusAdapter : Adapter() { +internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : Adapter() { private val items = ArrayList() + private var editMode = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppStatusViewHolder { val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false) @@ -30,28 +34,67 @@ internal class AppStatusAdapter : Adapter() { holder.bind(items[position]) } + fun setEditMode(enabled: Boolean) { + editMode = enabled + notifyDataSetChanged() + } + fun update(newItems: List, diff: DiffResult) { items.clear() items.addAll(newItems) diff.dispatchUpdatesTo(this) } + fun onItemChanged(item: AppStatus) { + val pos = items.indexOfFirst { it.packageName == item.packageName } + if (pos != NO_POSITION) notifyItemChanged(pos, item) + } + inner class AppStatusViewHolder(v: View) : AppViewHolder(v) { fun bind(item: AppStatus) { appName.text = item.name appIcon.setImageDrawable(item.icon) - setStatus(item.status) - if (item.status == SUCCEEDED) { - appInfo.text = item.time.toRelativeTime(context) - appInfo.visibility = VISIBLE + if (editMode) { + v.background = clickableBackground + v.setOnClickListener { + switchView.toggle() + item.enabled = switchView.isChecked + toggleListener.onAppStatusToggled(item) + } + appInfo.visibility = GONE + appStatus.visibility = INVISIBLE + progressBar.visibility = INVISIBLE + switchView.visibility = VISIBLE + switchView.isChecked = item.enabled + } else { + v.background = null + v.setOnClickListener(null) + setStatus(item.status) + if (item.status == SUCCEEDED) { + appInfo.text = item.time.toRelativeTime(context) + appInfo.visibility = VISIBLE + } + switchView.visibility = INVISIBLE } + // show disabled items differently + showEnabled(item.enabled) + } + + private fun showEnabled(enabled: Boolean) { + val alpha = if (enabled) 1.0f else 0.5f + // setting the alpha on root view v only doesn't work as the ItemAnimator messes with it + appIcon.alpha = alpha + appName.alpha = alpha + appInfo.alpha = alpha + appStatus.alpha = alpha } } } -internal data class AppStatus( +data class AppStatus( val packageName: String, + var enabled: Boolean, val icon: Drawable, val name: String, val time: Long, diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt index 69fc1a60..bd442158 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt @@ -2,6 +2,9 @@ package com.stevesoltys.seedvault.settings import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE @@ -13,15 +16,21 @@ import com.stevesoltys.seedvault.R import kotlinx.android.synthetic.main.fragment_app_status.* import org.koin.androidx.viewmodel.ext.android.sharedViewModel -class AppStatusFragment : Fragment() { +internal interface AppStatusToggleListener { + fun onAppStatusToggled(status: AppStatus) +} + +class AppStatusFragment : Fragment(), AppStatusToggleListener { private val viewModel: SettingsViewModel by sharedViewModel() private val layoutManager = LinearLayoutManager(context) - private val adapter = AppStatusAdapter() + private val adapter = AppStatusAdapter(this) + private lateinit var appEditMenuItem: MenuItem override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + setHasOptionsMenu(true) return inflater.inflate(R.layout.fragment_app_status, container, false) } @@ -42,4 +51,29 @@ class AppStatusFragment : Fragment() { }) } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.app_status_menu, menu) + appEditMenuItem = menu.findItem(R.id.edit_app_blacklist) + + // observe edit mode changes here where we are sure to have the MenuItem + viewModel.appEditMode.observe(this, Observer { enabled -> + appEditMenuItem.isChecked = enabled + adapter.setEditMode(enabled) + }) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + R.id.edit_app_blacklist -> { + viewModel.setEditMode(!item.isChecked) + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun onAppStatusToggled(status: AppStatus) { + adapter.onItemChanged(status) + viewModel.onAppStatusToggled(status) + } + } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt index 52202e23..949bc2d9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt @@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.settings import android.content.Context import android.hardware.usb.UsbDevice import android.net.Uri +import androidx.annotation.UiThread import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceManager import java.util.concurrent.atomic.AtomicBoolean @@ -18,12 +19,18 @@ private const val PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER = "flashSerialNumber" private const val PREF_KEY_FLASH_DRIVE_VENDOR_ID = "flashDriveVendorId" private const val PREF_KEY_FLASH_DRIVE_PRODUCT_ID = "flashDriveProductId" +private const val PREF_KEY_BACKUP_APP_BLACKLIST = "backupAppBlacklist" + class SettingsManager(context: Context) { private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private var isStorageChanging: AtomicBoolean = AtomicBoolean(false) + private val blacklistedApps: HashSet by lazy { + prefs.getStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, emptySet()).toHashSet() + } + // FIXME Storage is currently plugin specific and not generic fun setStorage(storage: Storage) { prefs.edit() @@ -76,6 +83,15 @@ class SettingsManager(context: Context) { return prefs.getBoolean(PREF_KEY_BACKUP_APK, true) } + fun isBackupEnabled(packageName: String) = !blacklistedApps.contains(packageName) + + @UiThread + fun onAppBackupStatusChanged(status: AppStatus) { + if (status.enabled) blacklistedApps.remove(status.packageName) + else blacklistedApps.add(status.packageName) + prefs.edit().putStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, blacklistedApps).apply() + } + } data class Storage( diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index 209da942..a841d2f1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -3,8 +3,10 @@ package com.stevesoltys.seedvault.settings import android.app.Application import android.content.pm.PackageManager.NameNotFoundException import android.util.Log +import androidx.annotation.UiThread import androidx.core.content.ContextCompat.getDrawable import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations.switchMap import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope @@ -47,6 +49,9 @@ class SettingsViewModel( private val mAppStatusList = switchMap(lastBackupTime) { getAppStatusResult() } internal val appStatusList: LiveData = mAppStatusList + private val mAppEditMode = MutableLiveData() + internal val appEditMode: LiveData = mAppEditMode + init { viewModelScope.launch(Dispatchers.IO) { // ensures the lastBackupTime LiveData gets set @@ -91,6 +96,7 @@ class SettingsViewModel( } AppStatus( packageName = it.packageName, + enabled = settingsManager.isBackupEnabled(it.packageName), icon = icon, name = getAppName(app, it.packageName).toString(), time = time, @@ -102,4 +108,14 @@ class SettingsViewModel( emit(AppStatusResult(list, diff)) } + @UiThread + fun setEditMode(enabled: Boolean) { + mAppEditMode.value = enabled + } + + @UiThread + fun onAppStatusToggled(status: AppStatus) { + settingsManager.onAppBackupStatusChanged(status) + } + } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 5b673ccd..e6df7cf1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -90,9 +90,13 @@ internal class BackupCoordinator( } fun isAppEligibleForBackup(targetPackage: PackageInfo, @Suppress("UNUSED_PARAMETER") isFullBackup: Boolean): Boolean { + val packageName = targetPackage.packageName + // Check that the app is not blacklisted by the user + val enabled = settingsManager.isBackupEnabled(packageName) + if (!enabled) Log.w(TAG, "Backup of $packageName disabled by user.") // We need to exclude the DocumentsProvider used to store backup data. // Otherwise, it gets killed when we back it up, terminating our backup. - return targetPackage.packageName != plugin.providerPackageName + return enabled && targetPackage.packageName != plugin.providerPackageName } /** @@ -104,9 +108,10 @@ internal class BackupCoordinator( * @return Current limit on backup size in bytes. */ fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long { - // try to back up APK here as later methods are sometimes not called called - val pm = context.packageManager - backUpApk(pm.getPackageInfo(packageName, GET_SIGNING_CERTIFICATES)) + if (packageName != MAGIC_PACKAGE_MANAGER) { + // try to back up APK here as later methods are sometimes not called called + backUpApk(context.packageManager.getPackageInfo(packageName, GET_SIGNING_CERTIFICATES)) + } // report back quota Log.i(TAG, "Get backup quota for $packageName. Is full backup: $isFullBackup.") diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt index 32ba6cf4..5c633c85 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/AppViewHolder.kt @@ -8,6 +8,7 @@ import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.widget.ImageView import android.widget.ProgressBar +import android.widget.Switch import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.stevesoltys.seedvault.R @@ -21,18 +22,26 @@ import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED -internal open class AppViewHolder(v: View) : RecyclerView.ViewHolder(v) { +internal open class AppViewHolder(protected val v: View) : RecyclerView.ViewHolder(v) { protected val context: Context = v.context protected val pm: PackageManager = context.packageManager + protected val clickableBackground = v.background!! protected val appIcon: ImageView = v.findViewById(R.id.appIcon) protected val appName: TextView = v.findViewById(R.id.appName) protected val appInfo: TextView = v.findViewById(R.id.appInfo) protected val appStatus: ImageView = v.findViewById(R.id.appStatus) protected val progressBar: ProgressBar = v.findViewById(R.id.progressBar) + protected val switchView: Switch = v.findViewById(R.id.switchView) + + init { + // don't use clickable background by default + v.background = null + } protected fun setStatus(status: AppRestoreStatus) { + v.background = null if (status == IN_PROGRESS) { appInfo.visibility = GONE appStatus.visibility = INVISIBLE diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt index 3f2559e4..6527fd2f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt @@ -8,7 +8,7 @@ import com.stevesoltys.seedvault.ui.storage.StorageViewModel abstract class RequireProvisioningViewModel( protected val app: Application, - private val settingsManager: SettingsManager, + protected val settingsManager: SettingsManager, private val keyManager: KeyManager ) : AndroidViewModel(app) { diff --git a/app/src/main/res/layout/fragment_app_status.xml b/app/src/main/res/layout/fragment_app_status.xml index 4dccb26c..904afb0b 100644 --- a/app/src/main/res/layout/fragment_app_status.xml +++ b/app/src/main/res/layout/fragment_app_status.xml @@ -8,9 +8,6 @@ android:id="@+id/list" android:layout_width="0dp" android:layout_height="0dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:scrollbarStyle="outsideOverlay" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/fragment_restore_progress.xml b/app/src/main/res/layout/fragment_restore_progress.xml index 68f9eeb9..3254843d 100644 --- a/app/src/main/res/layout/fragment_restore_progress.xml +++ b/app/src/main/res/layout/fragment_restore_progress.xml @@ -58,7 +58,8 @@ android:id="@+id/appList" android:layout_width="0dp" android:layout_height="0dp" - android:layout_margin="16dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/backupNameView" diff --git a/app/src/main/res/layout/list_item_app_status.xml b/app/src/main/res/layout/list_item_app_status.xml index e7a0236d..25801a7c 100644 --- a/app/src/main/res/layout/list_item_app_status.xml +++ b/app/src/main/res/layout/list_item_app_status.xml @@ -4,8 +4,11 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp"> + android:background="?android:selectableItemBackground" + android:paddingStart="16dp" + android:paddingTop="8dp" + android:paddingEnd="16dp" + android:paddingBottom="8dp"> @@ -57,10 +60,20 @@ + + diff --git a/app/src/main/res/menu/app_status_menu.xml b/app/src/main/res/menu/app_status_menu.xml new file mode 100644 index 00000000..ed96c800 --- /dev/null +++ b/app/src/main/res/menu/app_status_menu.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5e62f47..701963ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Disable app backup App backup status Last Backup: %1$s + Exclude apps Backup now diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt index 63c0e741..4136e361 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt @@ -23,7 +23,9 @@ import io.mockk.just import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.io.IOException import java.io.OutputStream @@ -122,6 +124,20 @@ internal class BackupCoordinatorTest : BackupTest() { assertEquals(quota, backup.getBackupQuota(packageInfo.packageName, isFullBackup)) } + @Test + fun `isAppEligibleForBackup() exempts plugin provider and blacklisted apps`() { + every { + settingsManager.isBackupEnabled(packageInfo.packageName) + } returns true andThen false andThen true + every { + plugin.providerPackageName + } returns packageInfo.packageName andThen "new.package" andThen "new.package" + + assertFalse(backup.isAppEligibleForBackup(packageInfo, true)) + assertFalse(backup.isAppEligibleForBackup(packageInfo, true)) + assertTrue(backup.isAppEligibleForBackup(packageInfo, true)) + } + @Test fun `clearing KV backup data throws`() { every { kv.clearBackupData(packageInfo) } throws IOException() From c52d3724426ae8b832dd5c4f5a937e5c44e9a58a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 20 Jan 2020 11:49:30 -0300 Subject: [PATCH 4/4] Don't make the app immersive in SetupWizard Fixes third item in #63 --- .../stevesoltys/seedvault/restore/RestoreActivity.kt | 2 -- .../com/stevesoltys/seedvault/ui/BackupActivity.kt | 11 ++--------- .../seedvault/ui/RequireProvisioningActivity.kt | 2 +- .../seedvault/ui/recoverycode/RecoveryCodeActivity.kt | 11 ++--------- .../seedvault/ui/storage/StorageActivity.kt | 2 -- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt index ff911602..98fb9f0e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt @@ -19,8 +19,6 @@ class RestoreActivity : RequireProvisioningActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (isSetupWizard) hideSystemUI() - setContentView(R.layout.activity_fragment_container) viewModel.displayFragment.observeEvent(this, LiveEventHandler { fragment -> diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt index 918a54c1..97378027 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt @@ -1,7 +1,6 @@ package com.stevesoltys.seedvault.ui import android.view.MenuItem -import android.view.View import androidx.annotation.CallSuper import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment @@ -10,8 +9,8 @@ import com.stevesoltys.seedvault.R abstract class BackupActivity : AppCompatActivity() { @CallSuper - override fun onOptionsItemSelected(item: MenuItem): Boolean = when { - item.itemId == android.R.id.home -> { + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + android.R.id.home -> { onBackPressed() true } @@ -25,10 +24,4 @@ abstract class BackupActivity : AppCompatActivity() { fragmentTransaction.commit() } - protected fun hideSystemUI() { - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_FULLSCREEN) - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt index cffe2bfc..bf129ef7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt @@ -24,7 +24,7 @@ private val TAG = RequireProvisioningActivity::class.java.name */ abstract class RequireProvisioningActivity : BackupActivity() { - protected val isSetupWizard: Boolean + private val isSetupWizard: Boolean get() = intent?.action == ACTION_SETUP_WIZARD protected abstract fun getViewModel(): RequireProvisioningViewModel diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt index 210f2640..dcfc7605 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt @@ -5,7 +5,6 @@ import android.view.MenuItem import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE -import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD import com.stevesoltys.seedvault.ui.LiveEventHandler import org.koin.androidx.viewmodel.ext.android.viewModel @@ -16,8 +15,6 @@ class RecoveryCodeActivity : BackupActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (isSetupWizard()) hideSystemUI() - setContentView(R.layout.activity_recovery_code) viewModel.isRestore = isRestore() @@ -38,8 +35,8 @@ class RecoveryCodeActivity : BackupActivity() { } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when { - item.itemId == android.R.id.home -> { + return when (item.itemId) { + android.R.id.home -> { onBackPressed() true } @@ -65,8 +62,4 @@ class RecoveryCodeActivity : BackupActivity() { return intent?.getBooleanExtra(INTENT_EXTRA_IS_RESTORE, false) ?: false } - private fun isSetupWizard(): Boolean { - return intent?.getBooleanExtra(INTENT_EXTRA_IS_SETUP_WIZARD, false) ?: false - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt index 7534c19b..d03a77e7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt @@ -22,8 +22,6 @@ class StorageActivity : BackupActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (isSetupWizard()) hideSystemUI() - setContentView(R.layout.activity_fragment_container) viewModel = if (isRestore()) {