mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-06-17 12:19:52 -04:00
Work around Chinese ROMs aborting installation pre-approval
There's a second code path for updates where we can't ask for pre-approval right away, so it needs to be triggered by the user when they see the UI. This code path gets hit for automatic updates or when the user wants to update many apps at once. There we also measure time to abort and if it is faster than 250ms we continue without pre-approval.
This commit is contained in:
@@ -347,13 +347,15 @@ constructor(
|
||||
suspend fun requestUserConfirmation(state: InstallConfirmationState): InstallState =
|
||||
safeSuspendCoroutine { cont ->
|
||||
val isPreApproval = state is InstallState.PreApprovalConfirmationNeeded
|
||||
val timeSource = TimeSource.Monotonic
|
||||
var preapprovalMark: TimeSource.Monotonic.ValueTimeMark? = null
|
||||
val receiver =
|
||||
receiverFactory.create(state.sessionId) { status, _, msg ->
|
||||
unregisterReceiver(this)
|
||||
when (status) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
val newState =
|
||||
if (isPreApproval)
|
||||
if (isPreApproval) {
|
||||
InstallState.PreApproved(
|
||||
name = state.name,
|
||||
versionName = state.versionName,
|
||||
@@ -362,7 +364,7 @@ constructor(
|
||||
iconModel = state.iconModel,
|
||||
result = PreApprovalResult.Success(state.sessionId),
|
||||
)
|
||||
else
|
||||
} else {
|
||||
InstallState.Installed(
|
||||
name = state.name,
|
||||
versionName = state.versionName,
|
||||
@@ -370,22 +372,39 @@ constructor(
|
||||
lastUpdated = state.lastUpdated,
|
||||
iconModel = state.iconModel,
|
||||
)
|
||||
}
|
||||
cont.resume(newState)
|
||||
}
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
error("Got STATUS_PENDING_USER_ACTION again")
|
||||
}
|
||||
else -> {
|
||||
if (status == PackageInstaller.STATUS_FAILURE_ABORTED) {
|
||||
cont.resume(InstallState.UserAborted)
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||
val mark = preapprovalMark
|
||||
if (isPreApproval && mark != null && mark.elapsedNow() < 250.milliseconds) {
|
||||
// As of 2026 some Chinese ROMs currently have not implemented pre-approval
|
||||
// and just return this error as if it was the user who aborted. See #3254
|
||||
// So we count fast aborts as not supported, so normal installation can proceed.
|
||||
log.warn { "Fast pre-approval abort for ${state.name}, trying without..." }
|
||||
val state = InstallState.PreApproved(
|
||||
name = state.name,
|
||||
versionName = state.versionName,
|
||||
currentVersionName = state.currentVersionName,
|
||||
lastUpdated = state.lastUpdated,
|
||||
iconModel = state.iconModel,
|
||||
result = PreApprovalResult.NotSupported,
|
||||
)
|
||||
cont.resume(state)
|
||||
} else {
|
||||
cont.resume(InstallState.Error(msg, state))
|
||||
log.info { "User aborted confirmation for ${state.name}" }
|
||||
cont.resume(InstallState.UserAborted)
|
||||
}
|
||||
}
|
||||
else -> cont.resume(InstallState.Error(msg, state))
|
||||
}
|
||||
}
|
||||
registerReceiver(context, receiver, IntentFilter(ACTION_INSTALL), RECEIVER_NOT_EXPORTED)
|
||||
cont.invokeOnCancellation { unregisterReceiver(receiver) }
|
||||
if (isPreApproval) preapprovalMark = timeSource.markNow()
|
||||
try {
|
||||
state.intent.send()
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -608,6 +608,38 @@ internal class SessionInstallManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `requestUserConfirmation fast pre-approval abort returns PreApproved with NotSupported`() {
|
||||
runBlocking {
|
||||
val appVersion: AppVersion = mockk(relaxed = true)
|
||||
val preApprovalState =
|
||||
InstallState.PreApprovalConfirmationNeeded(
|
||||
state =
|
||||
InstallState.Starting(
|
||||
name = "Example App",
|
||||
versionName = "1.0",
|
||||
currentVersionName = "0.9",
|
||||
lastUpdated = 42,
|
||||
iconModel = null,
|
||||
),
|
||||
version = appVersion,
|
||||
repo = mockk(relaxed = true),
|
||||
sessionId = sessionId,
|
||||
intent = pendingIntent,
|
||||
)
|
||||
// The callback fires synchronously inside intent.send(), so elapsedNow() < 250ms,
|
||||
// which triggers the fast-abort path that returns PreApproved(NotSupported).
|
||||
val result =
|
||||
requestUserConfirmationForStatus(
|
||||
preApprovalState,
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED,
|
||||
null,
|
||||
)
|
||||
assertIs<InstallState.PreApproved>(result)
|
||||
assertIs<PreApprovalResult.NotSupported>(result.result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `requestPreapproval not supported in China`(): Unit = runBlocking {
|
||||
every { telephonyManager.simCountryIso } returns "CN"
|
||||
|
||||
Reference in New Issue
Block a user