Make app blacklist accessible by multiple threads

Might fix #83
This commit is contained in:
Torsten Grote 2020-08-13 15:43:43 -03:00 committed by Chirayu Desai
parent 0b6742df44
commit 77ce3f6fe8
2 changed files with 76 additions and 64 deletions

View file

@ -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
) )
} }
} }

View file

@ -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))