Implement complete Android settings and recovery controls (#104)
This commit is contained in:
1 parent
06575363a2
commit
43f660ace3
34 files changed
+2720
-64
No files matched your search
+138
@@ -0,0 +1,138 @@
|
||||
package de.rfc1437.ironstorage.platform
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.credentials.CredentialManager
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.view.autofill.AutofillManager
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import de.rfc1437.ironstorage.core.ProviderStatus
|
||||
import de.rfc1437.ironstorage.core.SettingsPlatform
|
||||
|
||||
class AndroidSettingsPlatform(
|
||||
private val context: Context,
|
||||
private val keystore: AndroidKeystoreAdapter
|
||||
) : SettingsPlatform {
|
||||
private var picker: ActivityResultLauncher<String>? = null
|
||||
private var host: java.lang.ref.WeakReference<FragmentActivity> = java.lang.ref.WeakReference(
|
||||
null
|
||||
)
|
||||
fun attach(activity: FragmentActivity, selected: (List<String>) -> Unit) {
|
||||
host = java.lang.ref.WeakReference(activity)
|
||||
picker =
|
||||
activity.activityResultRegistry.register(
|
||||
"ironstorage.settings.qr-images",
|
||||
activity,
|
||||
ActivityResultContracts.GetMultipleContents()
|
||||
) { uris ->
|
||||
if (uris.isNotEmpty()) selected(uris.map { it.toString() })
|
||||
}
|
||||
}
|
||||
fun detach(activity: FragmentActivity) {
|
||||
if (host.get() === activity) {
|
||||
picker = null
|
||||
host.clear()
|
||||
}
|
||||
}
|
||||
override fun chooseQrImages(): Boolean {
|
||||
val picker = picker ?: return false
|
||||
return try {
|
||||
picker.launch("image/*")
|
||||
true
|
||||
} catch (
|
||||
_: android.content.ActivityNotFoundException
|
||||
) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun services(permission: String): List<ComponentName> =
|
||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SERVICES)
|
||||
.services.orEmpty().filter { it.permission == permission }
|
||||
.map { ComponentName(context.packageName, it.name) }
|
||||
override fun autofill(): ProviderStatus {
|
||||
val manager = context.getSystemService(AutofillManager::class.java)
|
||||
return ProviderStatus(
|
||||
manager?.isAutofillSupported == true,
|
||||
services("android.permission.BIND_AUTOFILL_SERVICE").isNotEmpty(),
|
||||
manager?.hasEnabledAutofillServices() == true
|
||||
)
|
||||
}
|
||||
override fun credentials(): ProviderStatus {
|
||||
val services = services("android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE")
|
||||
val enabled = if (Build.VERSION.SDK_INT >= 34) {
|
||||
val manager = context.getSystemService(CredentialManager::class.java)
|
||||
services.any { manager?.isEnabledCredentialProviderService(it) == true }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return ProviderStatus(Build.VERSION.SDK_INT >= 34, services.isNotEmpty(), enabled)
|
||||
}
|
||||
override fun biometricsAvailable() = keystore.deviceState().strongBiometric
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun version(): String = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
.versionName.orEmpty()
|
||||
private fun open(intent: Intent): Boolean = try {
|
||||
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
true
|
||||
} catch (
|
||||
_: android.content.ActivityNotFoundException
|
||||
) {
|
||||
false
|
||||
} catch (_: SecurityException) {
|
||||
false
|
||||
}
|
||||
override fun openAutofill(): Boolean {
|
||||
if (!autofill().supported) return false
|
||||
return open(
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE,
|
||||
"package:${context.packageName}".toUri()
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun openCredentials(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 34) return false
|
||||
val intent = if (Build.VERSION.SDK_INT >= 35) {
|
||||
Intent(Settings.ACTION_CREDENTIAL_PROVIDER, "package:${context.packageName}".toUri())
|
||||
} else {
|
||||
Intent(
|
||||
Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE,
|
||||
"package:${context.packageName}".toUri()
|
||||
)
|
||||
}
|
||||
return open(intent)
|
||||
}
|
||||
override suspend fun readQrImage(uri: String): ByteArray {
|
||||
val selected = uri.toUri()
|
||||
if (selected.scheme != "content") throw java.io.IOException("Not a content URI")
|
||||
return context.contentResolver.openInputStream(selected)?.use { input ->
|
||||
val output = java.io.ByteArrayOutputStream()
|
||||
val buffer = ByteArray(16384)
|
||||
while (true) {
|
||||
val count = input.read(buffer)
|
||||
if (count < 0) break
|
||||
if (output.size() + count >
|
||||
32 * 1024 * 1024
|
||||
) {
|
||||
throw java.io.IOException("Image exceeds the supported size")
|
||||
}
|
||||
output.write(buffer, 0, count)
|
||||
}
|
||||
val bytes = output.toByteArray()
|
||||
if (bytes.size > 32 * 1024 * 1024) {
|
||||
bytes.fill(0)
|
||||
throw java.io.IOException("Image exceeds the supported size")
|
||||
}
|
||||
bytes
|
||||
} ?: throw java.io.IOException("Image unavailable")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user