Skip to content

Commit

Permalink
Show backup status of select system apps
Browse files Browse the repository at this point in the history
  • Loading branch information
grote authored and chirayudesai committed Oct 29, 2020
1 parent a1b68df commit 81cd672
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 62 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.stevesoltys.seedvault.metadata.metadataModule
import com.stevesoltys.seedvault.plugins.saf.documentsProviderModule
import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.restore.install.installModule
import com.stevesoltys.seedvault.settings.AppListRetriever
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.SettingsViewModel
import com.stevesoltys.seedvault.transport.backup.backupModule
Expand All @@ -40,6 +41,7 @@ class App : Application() {
single { BackupNotificationManager(this@App) }
single { Clock() }
factory<IBackupManager> { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) }
factory { AppListRetriever(this@App, get(), get(), get()) }

viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get()) }
viewModel { RecoveryCodeViewModel(this@App, get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.stevesoltys.seedvault.settings

import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.ui.AppBackupState
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NOT_ALLOWED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NO_DATA
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_WAS_STOPPED
import com.stevesoltys.seedvault.ui.AppBackupState.NOT_YET_BACKED_UP
import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED
import com.stevesoltys.seedvault.ui.notification.getAppName
import java.util.Locale

private const val TAG = "AppListRetriever"

private const val PACKAGE_NAME_SMS = "com.android.providers.telephony"
private const val PACKAGE_NAME_SETTINGS = "com.android.providers.settings"
private const val PACKAGE_NAME_CALL_LOG = "com.android.calllogbackup"
private const val PACKAGE_NAME_CONTACTS = "org.calyxos.backup.contacts"

internal class AppListRetriever(
private val context: Context,
private val packageService: PackageService,
private val settingsManager: SettingsManager,
private val metadataManager: MetadataManager
) {

private val pm: PackageManager = context.packageManager

@WorkerThread
fun getAppList(): List<AppStatus> {
return getSpecialApps() + getUserApps()
}

private fun getSpecialApps(): List<AppStatus> {
val specialPackages = listOf(
Pair(PACKAGE_NAME_SMS, R.string.backup_sms),
Pair(PACKAGE_NAME_SETTINGS, R.string.backup_settings),
Pair(PACKAGE_NAME_CALL_LOG, R.string.backup_call_log),
Pair(PACKAGE_NAME_CONTACTS, R.string.backup_contacts)
)
return specialPackages.map { (packageName, stringId) ->
val metadata = metadataManager.getPackageMetadata(packageName)
AppStatus(
packageName = packageName,
enabled = settingsManager.isBackupEnabled(packageName),
icon = getIcon(packageName),
name = context.getString(stringId),
time = metadata?.time ?: 0,
status = metadata?.state.toAppBackupState(),
isSpecial = true
)
}
}

private fun getUserApps(): List<AppStatus> {
val locale = Locale.getDefault()
return packageService.userApps.map {
val metadata = metadataManager.getPackageMetadata(it.packageName)
val time = metadata?.time ?: 0
val status = metadata?.state.toAppBackupState()
if (status == NOT_YET_BACKED_UP) {
Log.w(TAG, "No metadata available for: ${it.packageName}")
}
if (metadata?.hasApk() == false) {
Log.w(TAG, "No APK stored for: ${it.packageName}")
}
AppStatus(
packageName = it.packageName,
enabled = settingsManager.isBackupEnabled(it.packageName),
icon = getIcon(it.packageName),
name = getAppName(context, it.packageName).toString(),
time = time,
status = status
)
}.sortedBy { it.name.toLowerCase(locale) }
}

private fun getIcon(packageName: String): Drawable = when (packageName) {
MAGIC_PACKAGE_MANAGER -> context.getDrawable(R.drawable.ic_launcher_default)!!
PACKAGE_NAME_SMS -> context.getDrawable(R.drawable.ic_message)!!
PACKAGE_NAME_SETTINGS -> context.getDrawable(R.drawable.ic_settings)!!
PACKAGE_NAME_CALL_LOG -> context.getDrawable(R.drawable.ic_call)!!
PACKAGE_NAME_CONTACTS -> context.getDrawable(R.drawable.ic_contacts)!!
else -> getIconFromPackageManager(packageName)
}

private fun getIconFromPackageManager(packageName: String): Drawable = try {
pm.getApplicationIcon(packageName)
} catch (e: PackageManager.NameNotFoundException) {
context.getDrawable(R.drawable.ic_launcher_default)!!
}

private fun PackageState?.toAppBackupState(): AppBackupState = when (this) {
null -> NOT_YET_BACKED_UP
PackageState.NO_DATA -> FAILED_NO_DATA
PackageState.WAS_STOPPED -> FAILED_WAS_STOPPED
PackageState.NOT_ALLOWED -> FAILED_NOT_ALLOWED
PackageState.QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
PackageState.UNKNOWN_ERROR -> FAILED
PackageState.APK_AND_DATA -> SUCCEEDED
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView.ScaleType
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DiffUtil.DiffResult
Expand Down Expand Up @@ -59,6 +60,7 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
inner class AppStatusViewHolder(v: View) : AppViewHolder(v) {
fun bind(item: AppStatus) {
appName.text = item.name
appIcon.scaleType = if (item.isSpecial) ScaleType.CENTER else ScaleType.FIT_CENTER
appIcon.setImageDrawable(item.icon)
v.background = clickableBackground
if (editMode) {
Expand Down Expand Up @@ -110,7 +112,8 @@ data class AppStatus(
val icon: Drawable,
val name: String,
val time: Long,
val status: AppBackupState
val status: AppBackupState,
val isSpecial: Boolean = false
)

internal class AppStatusDiff(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,41 @@
package com.stevesoltys.seedvault.settings

import android.app.Application
import android.content.pm.PackageManager.NameNotFoundException
import android.database.ContentObserver
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.Uri
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
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
import androidx.recyclerview.widget.DiffUtil.calculateDiff
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NOT_ALLOWED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NO_DATA
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_WAS_STOPPED
import com.stevesoltys.seedvault.ui.AppBackupState.NOT_YET_BACKED_UP
import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.getAppName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale

private const val USER_FULL_DATA_BACKUP_AWARE = "user_full_data_backup_aware"

private val TAG = SettingsViewModel::class.java.simpleName

internal class SettingsViewModel(
app: Application,
settingsManager: SettingsManager,
keyManager: KeyManager,
private val notificationManager: BackupNotificationManager,
private val metadataManager: MetadataManager,
private val packageService: PackageService
private val appListRetriever: AppListRetriever
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {

private val contentResolver = app.contentResolver
Expand Down Expand Up @@ -154,44 +132,7 @@ internal class SettingsViewModel(
}

private fun getAppStatusResult(): LiveData<AppStatusResult> = liveData(Dispatchers.Default) {
val pm = app.packageManager
val locale = Locale.getDefault()
val list = packageService.userApps.map {
val icon = if (it.packageName == MAGIC_PACKAGE_MANAGER) {
getDrawable(app, R.drawable.ic_launcher_default)!!
} else {
try {
pm.getApplicationIcon(it.packageName)
} catch (e: NameNotFoundException) {
getDrawable(app, R.drawable.ic_launcher_default)!!
}
}
val metadata = metadataManager.getPackageMetadata(it.packageName)
val time = metadata?.time ?: 0
val status = when (metadata?.state) {
null -> {
Log.w(TAG, "No metadata available for: ${it.packageName}")
NOT_YET_BACKED_UP
}
NO_DATA -> FAILED_NO_DATA
WAS_STOPPED -> FAILED_WAS_STOPPED
NOT_ALLOWED -> FAILED_NOT_ALLOWED
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
UNKNOWN_ERROR -> FAILED
APK_AND_DATA -> SUCCEEDED
}
if (metadata?.hasApk() == false) {
Log.w(TAG, "No APK stored for: ${it.packageName}")
}
AppStatus(
packageName = it.packageName,
enabled = settingsManager.isBackupEnabled(it.packageName),
icon = icon,
name = getAppName(app, it.packageName).toString(),
time = time,
status = status
)
}.sortedBy { it.name.toLowerCase(locale) }
val list = appListRetriever.getAppList()
val oldList = mAppStatusList.value?.appStatusList ?: emptyList()
val diff = calculateDiff(AppStatusDiff(oldList, list))
emit(AppStatusResult(list, diff))
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_call.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20.01,15.38c-1.23,0 -2.42,-0.2 -3.53,-0.56 -0.35,-0.12 -0.74,-0.03 -1.01,0.24l-1.57,1.97c-2.83,-1.35 -5.48,-3.9 -6.89,-6.83l1.95,-1.66c0.27,-0.28 0.35,-0.67 0.24,-1.02 -0.37,-1.11 -0.56,-2.3 -0.56,-3.53 0,-0.54 -0.45,-0.99 -0.99,-0.99H4.19C3.65,3 3,3.24 3,3.99 3,13.28 10.73,21 20.01,21c0.71,0 0.99,-0.63 0.99,-1.18v-3.45c0,-0.54 -0.45,-0.99 -0.99,-0.99z" />
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_contacts.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z" />
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_message.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z" />
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
</vector>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@

<!-- App Backup and Restore State -->

<string name="backup_sms">SMS text messages</string>
<string name="backup_settings">Device settings</string>
<string name="backup_call_log">Call history</string>
<string name="backup_contacts">Local contacts</string>
<!-- This text gets shown for apps that the OS did not try to backup for whatever reason e.g. no backup was run yet -->
<string name="backup_app_not_yet_backed_up">Waiting to back up…</string>
<string name="restore_app_not_yet_backed_up">Was not yet backed up</string>
Expand Down

0 comments on commit 81cd672

Please sign in to comment.