mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 07:18:49 +02:00
fix(android): sanitize restored installation state
This commit is contained in:
@@ -21,6 +21,9 @@
|
||||
android:label="SpotiFLAC Mobile"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="@xml/backup_rules_legacy"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:usesCleartextTraffic="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
|
||||
@@ -1980,6 +1980,48 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an install-scoped marker in noBackupFilesDir. A platform restore
|
||||
* can bring SharedPreferences back after reinstall, but this marker is never
|
||||
* backed up. Package timestamps distinguish that case from the first app
|
||||
* update after this marker was introduced, so existing users are not sent
|
||||
* through onboarding again.
|
||||
*/
|
||||
private fun ensureInstallMarker(): Map<String, Any> {
|
||||
val marker = File(noBackupFilesDir, "installation_state_v1")
|
||||
val markerExisted = marker.isFile
|
||||
val packageInfo = @Suppress("DEPRECATION")
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
val installTimestampDelta = kotlin.math.abs(
|
||||
packageInfo.lastUpdateTime - packageInfo.firstInstallTime
|
||||
)
|
||||
val looksLikeFreshPackageInstall = installTimestampDelta <= 10_000L
|
||||
|
||||
var markerCreated = markerExisted
|
||||
if (!markerExisted) {
|
||||
markerCreated = try {
|
||||
marker.parentFile?.mkdirs()
|
||||
marker.writeText(
|
||||
"created_at=${System.currentTimeMillis()}\n" +
|
||||
"version_code=${BuildConfig.VERSION_CODE}\n"
|
||||
)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w(
|
||||
"SpotiFLAC",
|
||||
"Failed to create installation marker: ${e.message}"
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
"marker_existed" to markerExisted,
|
||||
"marker_created" to markerCreated,
|
||||
"fresh_package_install" to looksLikeFreshPackageInstall,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Ensure the shared audio_service engine exists before the activity
|
||||
// delegate looks it up by cached id (see getCachedEngineId above).
|
||||
@@ -2189,6 +2231,12 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
scope.launch {
|
||||
try {
|
||||
when (call.method) {
|
||||
"ensureInstallMarker" -> {
|
||||
val installState = withContext(Dispatchers.IO) {
|
||||
ensureInstallMarker()
|
||||
}
|
||||
result.success(installState)
|
||||
}
|
||||
"exitApp" -> {
|
||||
flutterBackCallback?.isEnabled = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<!--
|
||||
SpotiFLAC Mobile provides an explicit, schema-aware Backup & Restore
|
||||
feature. Platform backup must not copy device-bound SAF grants, paths,
|
||||
queues, extension credentials, or signed sessions to another install.
|
||||
-->
|
||||
<cloud-backup>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<!-- Android 11 and lower use this legacy rule format. -->
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="." />
|
||||
<exclude domain="device_root" path="." />
|
||||
<exclude domain="device_file" path="." />
|
||||
<exclude domain="device_database" path="." />
|
||||
<exclude domain="device_sharedpref" path="." />
|
||||
</full-backup-content>
|
||||
Reference in New Issue
Block a user