parent
0b6742df44
commit
77ce3f6fe8
2 changed files with 76 additions and 64 deletions
|
@ -6,6 +6,7 @@ import android.net.Uri
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
internal const val PREF_KEY_BACKUP_APK = "backup_apk"
|
internal const val PREF_KEY_BACKUP_APK = "backup_apk"
|
||||||
|
@ -27,24 +28,30 @@ class SettingsManager(context: Context) {
|
||||||
|
|
||||||
private var isStorageChanging: AtomicBoolean = AtomicBoolean(false)
|
private var isStorageChanging: AtomicBoolean = AtomicBoolean(false)
|
||||||
|
|
||||||
private val blacklistedApps: HashSet<String> by lazy {
|
/**
|
||||||
prefs.getStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, emptySet()).toHashSet()
|
* This gets accessed by non-UI threads when saving with [PreferenceManager]
|
||||||
|
* and when [isBackupEnabled] is called during a backup run.
|
||||||
|
* Therefore, it is implemented with a thread-safe [ConcurrentSkipListSet].
|
||||||
|
*/
|
||||||
|
private val blacklistedApps: MutableSet<String> by lazy {
|
||||||
|
ConcurrentSkipListSet(prefs.getStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, emptySet()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME Storage is currently plugin specific and not generic
|
// FIXME Storage is currently plugin specific and not generic
|
||||||
fun setStorage(storage: Storage) {
|
fun setStorage(storage: Storage) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
||||||
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
||||||
.putBoolean(PREF_KEY_STORAGE_IS_USB, storage.isUsb)
|
.putBoolean(PREF_KEY_STORAGE_IS_USB, storage.isUsb)
|
||||||
.apply()
|
.apply()
|
||||||
isStorageChanging.set(true)
|
isStorageChanging.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStorage(): Storage? {
|
fun getStorage(): Storage? {
|
||||||
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
||||||
val uri = Uri.parse(uriStr)
|
val uri = Uri.parse(uriStr)
|
||||||
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException("no storage name")
|
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null)
|
||||||
|
?: throw IllegalStateException("no storage name")
|
||||||
val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false)
|
val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false)
|
||||||
return Storage(uri, name, isUsb)
|
return Storage(uri, name, isUsb)
|
||||||
}
|
}
|
||||||
|
@ -57,18 +64,18 @@ class SettingsManager(context: Context) {
|
||||||
fun setFlashDrive(usb: FlashDrive?) {
|
fun setFlashDrive(usb: FlashDrive?) {
|
||||||
if (usb == null) {
|
if (usb == null) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_NAME)
|
.remove(PREF_KEY_FLASH_DRIVE_NAME)
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER)
|
.remove(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER)
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_VENDOR_ID)
|
.remove(PREF_KEY_FLASH_DRIVE_VENDOR_ID)
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_PRODUCT_ID)
|
.remove(PREF_KEY_FLASH_DRIVE_PRODUCT_ID)
|
||||||
.apply()
|
.apply()
|
||||||
} else {
|
} else {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(PREF_KEY_FLASH_DRIVE_NAME, usb.name)
|
.putString(PREF_KEY_FLASH_DRIVE_NAME, usb.name)
|
||||||
.putString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, usb.serialNumber)
|
.putString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, usb.serialNumber)
|
||||||
.putInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, usb.vendorId)
|
.putInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, usb.vendorId)
|
||||||
.putInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, usb.productId)
|
.putInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, usb.productId)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,24 +103,26 @@ class SettingsManager(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Storage(
|
data class Storage(
|
||||||
val uri: Uri,
|
val uri: Uri,
|
||||||
val name: String,
|
val name: String,
|
||||||
val isUsb: Boolean) {
|
val isUsb: Boolean
|
||||||
|
) {
|
||||||
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
|
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
|
||||||
?: throw AssertionError("Should only happen on API < 21.")
|
?: throw AssertionError("Should only happen on API < 21.")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FlashDrive(
|
data class FlashDrive(
|
||||||
val name: String,
|
val name: String,
|
||||||
val serialNumber: String?,
|
val serialNumber: String?,
|
||||||
val vendorId: Int,
|
val vendorId: Int,
|
||||||
val productId: Int) {
|
val productId: Int
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun from(device: UsbDevice) = FlashDrive(
|
fun from(device: UsbDevice) = FlashDrive(
|
||||||
name = "${device.manufacturerName} ${device.productName}",
|
name = "${device.manufacturerName} ${device.productName}",
|
||||||
serialNumber = device.serialNumber,
|
serialNumber = device.serialNumber,
|
||||||
vendorId = device.vendorId,
|
vendorId = device.vendorId,
|
||||||
productId = device.productId
|
productId = device.productId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,10 @@ class SettingsViewModel(
|
||||||
|
|
||||||
internal val lastBackupTime = metadataManager.lastBackupTime
|
internal val lastBackupTime = metadataManager.lastBackupTime
|
||||||
|
|
||||||
private val mAppStatusList = switchMap(lastBackupTime) { getAppStatusResult() }
|
private val mAppStatusList = switchMap(lastBackupTime) {
|
||||||
|
// updates app list when lastBackupTime changes
|
||||||
|
getAppStatusResult()
|
||||||
|
}
|
||||||
internal val appStatusList: LiveData<AppStatusResult> = mAppStatusList
|
internal val appStatusList: LiveData<AppStatusResult> = mAppStatusList
|
||||||
|
|
||||||
private val mAppEditMode = MutableLiveData<Boolean>()
|
private val mAppEditMode = MutableLiveData<Boolean>()
|
||||||
|
@ -63,46 +66,46 @@ class SettingsViewModel(
|
||||||
Thread { requestBackup(app) }.start()
|
Thread { requestBackup(app) }.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppStatusResult(): LiveData<AppStatusResult> = liveData(Dispatchers.Main) {
|
private fun getAppStatusResult(): LiveData<AppStatusResult> = liveData {
|
||||||
val pm = app.packageManager
|
val pm = app.packageManager
|
||||||
val locale = Locale.getDefault()
|
val locale = Locale.getDefault()
|
||||||
val list = pm.getInstalledPackages(0)
|
val list = pm.getInstalledPackages(0)
|
||||||
.filter { !it.isSystemApp() }
|
.filter { !it.isSystemApp() }
|
||||||
.map {
|
.map {
|
||||||
val icon = if (it.packageName == MAGIC_PACKAGE_MANAGER) {
|
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)!!
|
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 metadata = metadataManager.getPackageMetadata(it.packageName)
|
||||||
val status = when (metadata?.state) {
|
val time = metadata?.time ?: 0
|
||||||
null -> {
|
val status = when (metadata?.state) {
|
||||||
Log.w(TAG, "No metadata available for: ${it.packageName}")
|
null -> {
|
||||||
FAILED
|
Log.w(TAG, "No metadata available for: ${it.packageName}")
|
||||||
}
|
FAILED
|
||||||
NO_DATA -> FAILED_NO_DATA
|
|
||||||
NOT_ALLOWED -> FAILED_NOT_ALLOWED
|
|
||||||
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
|
|
||||||
UNKNOWN_ERROR -> FAILED
|
|
||||||
APK_AND_DATA -> SUCCEEDED
|
|
||||||
}
|
}
|
||||||
if (metadata?.hasApk() == false) {
|
NO_DATA -> FAILED_NO_DATA
|
||||||
Log.w(TAG, "No APK stored for: ${it.packageName}")
|
NOT_ALLOWED -> FAILED_NOT_ALLOWED
|
||||||
}
|
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
|
||||||
AppStatus(
|
UNKNOWN_ERROR -> FAILED
|
||||||
packageName = it.packageName,
|
APK_AND_DATA -> SUCCEEDED
|
||||||
enabled = settingsManager.isBackupEnabled(it.packageName),
|
}
|
||||||
icon = icon,
|
if (metadata?.hasApk() == false) {
|
||||||
name = getAppName(app, it.packageName).toString(),
|
Log.w(TAG, "No APK stored for: ${it.packageName}")
|
||||||
time = time,
|
}
|
||||||
status = status
|
AppStatus(
|
||||||
)
|
packageName = it.packageName,
|
||||||
}.sortedBy { it.name.toLowerCase(locale) }
|
enabled = settingsManager.isBackupEnabled(it.packageName),
|
||||||
|
icon = icon,
|
||||||
|
name = getAppName(app, it.packageName).toString(),
|
||||||
|
time = time,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
}.sortedBy { it.name.toLowerCase(locale) }
|
||||||
val oldList = mAppStatusList.value?.appStatusList ?: emptyList()
|
val oldList = mAppStatusList.value?.appStatusList ?: emptyList()
|
||||||
val diff = calculateDiff(AppStatusDiff(oldList, list))
|
val diff = calculateDiff(AppStatusDiff(oldList, list))
|
||||||
emit(AppStatusResult(list, diff))
|
emit(AppStatusResult(list, diff))
|
||||||
|
|
Loading…
Reference in a new issue