Skip to content

Commit 6ec8543

Browse files
zjnsMarcaDian
andcommitted
fix: Cancelled install callbacks on some OEM systems (#598)
Co-authored-by: MarcaDian <152095496+MarcaDian@users.noreply.github.com>
1 parent 62b4859 commit 6ec8543

2 files changed

Lines changed: 50 additions & 3 deletions

File tree

app/src/main/java/app/morphe/manager/ui/viewmodel/InstallViewModel.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import app.morphe.manager.domain.installer.*
1717
import app.morphe.manager.domain.manager.PreferencesManager
1818
import app.morphe.manager.util.AppDataResolver
1919
import app.morphe.manager.util.PM
20+
import app.morphe.manager.util.sha256OrNull
2021
import app.morphe.manager.util.simpleMessage
2122
import app.morphe.manager.util.toast
2223
import kotlinx.coroutines.*
@@ -374,9 +375,14 @@ class InstallViewModel : ViewModel(), KoinComponent {
374375
val result = try {
375376
sessionInstaller.installInternal(outputFile)
376377
} catch (_: InstallCancelledException) {
377-
// User dismissed the dialog - go back to Ready immediately, no error shown
378-
installState = InstallState.Ready
379-
return
378+
if (confirmInstallCompleted(outputFile, targetPackageName)) {
379+
Log.w(TAG, "Install callback reported cancelled but APK SHA-256 verification succeeded for $targetPackageName")
380+
InstallResult.Success
381+
} else {
382+
// User dismissed the dialog and the installed APK does not match the patched APK.
383+
installState = InstallState.Ready
384+
return
385+
}
380386
} catch (_: SessionDeadException) {
381387
Log.w(TAG, "Session dead, falling back to intent-based install")
382388
launchIntentBasedFallback(outputFile, targetPackageName, onPersistApp)
@@ -398,6 +404,18 @@ class InstallViewModel : ViewModel(), KoinComponent {
398404
}
399405
}
400406

407+
private suspend fun confirmInstallCompleted(
408+
outputFile: File,
409+
targetPackageName: String
410+
): Boolean = withContext(Dispatchers.IO) {
411+
val installedFile = pm.getApplicationInfo(targetPackageName)?.sourceDir
412+
?.takeIf { it.isNotEmpty() }
413+
?.let(::File) ?: return@withContext false
414+
val installedHash = installedFile.sha256OrNull() ?: return@withContext false
415+
val expectedHash = outputFile.sha256OrNull() ?: return@withContext false
416+
installedHash == expectedHash
417+
}
418+
401419
/**
402420
* Silent install via Shizuku/Sui.
403421
*/

app/src/main/java/app/morphe/manager/util/PM.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import android.annotation.SuppressLint
44
import android.app.Application
55
import android.content.Context
66
import android.content.Intent
7+
import android.content.pm.ApplicationInfo
78
import android.content.pm.PackageInfo
89
import android.content.pm.PackageManager
10+
import android.content.pm.PackageManager.ApplicationInfoFlags
911
import android.content.pm.PackageManager.NameNotFoundException
1012
import android.content.pm.PackageManager.PackageInfoFlags
1113
import android.content.pm.Signature
@@ -41,6 +43,7 @@ class PM(
4143

4244
val application: Application get() = app
4345

46+
@Suppress("DEPRECATION")
4447
fun getPackageInfo(packageName: String, flags: Int = 0): PackageInfo? =
4548
try {
4649
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
@@ -51,6 +54,17 @@ class PM(
5154
null
5255
}
5356

57+
@Suppress("DEPRECATION")
58+
fun getApplicationInfo(packageName: String, flags: Int = 0): ApplicationInfo? =
59+
try {
60+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
61+
app.packageManager.getApplicationInfo(packageName, ApplicationInfoFlags.of(flags.toLong()))
62+
else
63+
app.packageManager.getApplicationInfo(packageName, flags)
64+
} catch (_: NameNotFoundException) {
65+
null
66+
}
67+
5468
fun getPackageInfo(file: File): PackageInfo? {
5569
val path = file.absolutePath
5670
val flags = PackageManager.GET_META_DATA or PackageManager.GET_ACTIVITIES
@@ -212,6 +226,21 @@ class PM(
212226
}
213227
}
214228

229+
fun File.sha256OrNull(): String? = runCatching {
230+
if (!isFile) return@runCatching null
231+
val digest = MessageDigest.getInstance("SHA-256")
232+
inputStream().use { input ->
233+
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
234+
while (!Thread.currentThread().isInterrupted) {
235+
val read = input.read(buffer)
236+
if (read < 0) break
237+
digest.update(buffer, 0, read)
238+
}
239+
}
240+
if (Thread.currentThread().isInterrupted) return@runCatching null
241+
digest.digest().joinToString("") { byte -> "%02x".format(byte) }
242+
}.getOrNull()
243+
215244
/** Opens the system screen that lets the user grant the "install unknown apps" permission. */
216245
object RequestInstallAppsContract : ActivityResultContract<String, Boolean>(), KoinComponent {
217246
private val pm: PM by inject()

0 commit comments

Comments
 (0)