Skip to content

Commit ce1ce6e

Browse files
authored
fix: Session is dead error on Pixels devices when installing apps (#458)
1 parent 9631a15 commit ce1ce6e

35 files changed

Lines changed: 151 additions & 355 deletions

app/build.gradle.kts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,6 @@ dependencies {
6262
implementation(libs.morphe.patcher)
6363
implementation(libs.morphe.library)
6464

65-
// Exclude xmlpull as it's included in Android already
66-
configurations.configureEach {
67-
exclude(group = "xmlpull", module = "xmlpull")
68-
// Ackpine uses android-stubs as compileOnly internally — exclude the transitive
69-
// dependency since the project already provides hidden API stubs via hidden-api-stub
70-
exclude(group = "ru.solrudev.ackpine", module = "android-stubs")
71-
}
72-
7365
implementation(libs.androidx.documentfile)
7466

7567
// Native processes

app/src/main/java/app/morphe/manager/ManagerApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class ManagerApplication : Application() {
6262
)
6363
}
6464

65-
// Enable Ackpine logging so install failures (session-dead, conflict codes, etc.)
65+
// Enable Ackpine logging
6666
Ackpine.enableLogcatLogger()
6767

6868
// App icon loader (Coil)

app/src/main/java/app/morphe/manager/data/room/apps/original/OriginalApk.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package app.morphe.manager.data.room.apps.original
33
import androidx.room.ColumnInfo
44
import androidx.room.Entity
55
import androidx.room.PrimaryKey
6-
import java.io.File
76

87
@Entity(tableName = "original_apks")
98
data class OriginalApk(
@@ -13,6 +12,4 @@ data class OriginalApk(
1312
@ColumnInfo(name = "file_path") val filePath: String,
1413
@ColumnInfo(name = "last_used") val lastUsed: Long = System.currentTimeMillis(),
1514
@ColumnInfo(name = "file_size") val fileSize: Long
16-
) {
17-
fun getFile(): File = File(filePath)
18-
}
15+
)

app/src/main/java/app/morphe/manager/di/HttpModule.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,17 @@ import org.koin.android.ext.koin.androidContext
1616
import org.koin.core.module.dsl.singleOf
1717
import org.koin.dsl.module
1818
import java.net.Inet4Address
19-
import java.net.InetAddress
2019

2120
val httpModule = module {
2221
fun provideHttpClient(context: Context, json: Json) = HttpClient(OkHttp) {
2322
engine {
2423
config {
25-
dns(object : Dns {
26-
override fun lookup(hostname: String): List<InetAddress> {
27-
val addresses = Dns.SYSTEM.lookup(hostname)
28-
val ipv4Addresses = addresses.filterIsInstance<Inet4Address>()
29-
// Force IPv4 if available, fallback to IPv6 only if no IPv4 addresses are found
30-
return ipv4Addresses.ifEmpty { addresses }
31-
}
32-
})
24+
dns { hostname ->
25+
val addresses = Dns.SYSTEM.lookup(hostname)
26+
val ipv4Addresses = addresses.filterIsInstance<Inet4Address>()
27+
// Force IPv4 if available, fallback to IPv6 only if no IPv4 addresses are found
28+
ipv4Addresses.ifEmpty { addresses }
29+
}
3330
// Force HTTP/1.1 to avoid intermittent HTTP/2 PROTOCOL_ERROR stream resets when
3431
// downloading patch bundles from GitHub-backed endpoints.
3532
protocols(listOf(Protocol.HTTP_1_1))

app/src/main/java/app/morphe/manager/domain/bundles/PatchBundleSource.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,12 @@ sealed class PatchBundleSource(
115115
val host = uri.host?.lowercase(java.util.Locale.US) ?: return null
116116
val segments = uri.path?.trim('/')?.split('/')?.filter { it.isNotBlank() } ?: return null
117117

118-
when {
118+
when (host) {
119119
// raw.githubusercontent.com/owner/repo/...
120-
host == "raw.githubusercontent.com" && segments.isNotEmpty() -> segments[0]
120+
"raw.githubusercontent.com" if segments.isNotEmpty() -> segments[0]
121121

122122
// github.com/owner/repo/...
123-
host == "github.com" && segments.isNotEmpty() -> segments[0]
124-
123+
"github.com" if segments.isNotEmpty() -> segments[0]
125124
else -> null
126125
}
127126
} catch (_: Exception) {

app/src/main/java/app/morphe/manager/domain/installer/AckpineInstaller.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import ru.solrudev.ackpine.installer.InstallFailure
1313
import ru.solrudev.ackpine.installer.PackageInstaller
1414
import ru.solrudev.ackpine.installer.parameters.InstallParameters
1515
import ru.solrudev.ackpine.installer.parameters.InstallerType
16-
import ru.solrudev.ackpine.session.await
16+
import ru.solrudev.ackpine.installer.parameters.PackageSource
1717
import ru.solrudev.ackpine.session.Session
18+
import ru.solrudev.ackpine.session.await
1819
import ru.solrudev.ackpine.session.parameters.Confirmation
1920
import ru.solrudev.ackpine.shizuku.ShizukuPlugin
2021
import ru.solrudev.ackpine.uninstaller.PackageUninstaller
@@ -42,7 +43,7 @@ class AckpineInstaller(private val app: Application) {
4243

4344
/**
4445
* Installs an APK using the standard Android PackageInstaller API via Ackpine.
45-
* Suspends until the user confirms or cancels the system dialog - no timeout needed.
46+
* Suspends until the user confirms or cancels the system dialog.
4647
*
4748
* @return null on success, or a typed [InstallFailure] the caller can pattern-match on.
4849
* @throws InstallCancelledException when the user dismisses the system install dialog.
@@ -55,6 +56,8 @@ class AckpineInstaller(private val app: Application) {
5556
.setInstallerType(InstallerType.SESSION_BASED)
5657
.setConfirmation(Confirmation.IMMEDIATE)
5758
.setName(apkFile.name)
59+
// PackageSource.LocalFile disables "restricted settings" enforcement on API 33+
60+
.setPackageSource(PackageSource.LocalFile)
5861
.build()
5962
)
6063
return try {
@@ -103,8 +106,8 @@ class AckpineInstaller(private val app: Application) {
103106
Log.i(TAG, "installShizuku succeeded: ${apkFile.name}")
104107
}
105108
}
106-
} catch (_: CancellationException) {
107-
throw InstallCancelledException()
109+
} catch (e: CancellationException) {
110+
throw InstallCancelledException().initCause(e)
108111
} catch (e: Exception) {
109112
Log.w(TAG, "installShizuku exception: ${e.message}", e)
110113
throw e
@@ -131,8 +134,8 @@ class AckpineInstaller(private val app: Application) {
131134
throw UninstallFailedException(result.failure.message ?: result.failure.javaClass.simpleName)
132135
}
133136
}
134-
} catch (_: CancellationException) {
135-
throw UninstallCancelledException()
137+
} catch (e: CancellationException) {
138+
throw UninstallCancelledException().initCause(e)
136139
}
137140
}
138141

app/src/main/java/app/morphe/manager/patcher/runtime/ProcessRuntime.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ import kotlin.math.max
4040
const val PROCESS_RUNTIME_MEMORY_MINIMUM = 512
4141
const val PROCESS_RUNTIME_MEMORY_MAX_LIMIT = 1280
4242
const val PROCESS_RUNTIME_MEMORY_MAX_LIMIT_INITIALIZATION = 1024
43-
private const val PROCESS_RUNTIME_MEMORY_DEFAULT = 640
4443
private const val PROCESS_RUNTIME_MEMORY_DEFAULT_MINIMUM = 640
4544
const val PROCESS_RUNTIME_MEMORY_LOW_WARNING = 640
46-
const val PROCESS_RUNTIME_MEMORY_STEP = 128
45+
const val PROCESS_RUNTIME_MEMORY_STEP = 64
4746

4847
// Sentinel value indicating the memory limit has never been set
4948
// triggers adaptive calculation on first use
@@ -117,9 +116,8 @@ class ProcessRuntime(
117116
skipUnneededSplits: Boolean,
118117
onMergedApkReady: (suspend (File) -> Unit)?,
119118
) = coroutineScope {
120-
val minMemoryLimit = 200
119+
val minMemoryLimit = 256
121120
var memoryMB = max(minMemoryLimit, prefs.patcherProcessMemoryLimit.get())
122-
var retried = false
123121

124122
while (true) {
125123
try {
@@ -136,16 +134,6 @@ class ProcessRuntime(
136134
onProgress,
137135
onMergedApkReady
138136
)
139-
// Success - update preference and return.
140-
if (retried && prefs.patcherProcessMemoryLimit.get() != memoryMB) {
141-
if (memoryMB < PROCESS_RUNTIME_MEMORY_DEFAULT) {
142-
// Don't save a value lower than the expected minimum.
143-
// Instead, allow discovering the actual memory limit again next time.
144-
memoryMB = PROCESS_RUNTIME_MEMORY_DEFAULT
145-
}
146-
Log.i(tag, "Updating process memory limit setting to: $memoryMB")
147-
prefs.patcherProcessMemoryLimit.update(memoryMB)
148-
}
149137

150138
return@coroutineScope
151139
} catch (e: Exception) {
@@ -156,7 +144,6 @@ class ProcessRuntime(
156144
}
157145

158146
if (isMemoryFailure && !skipMemoryRetry && memoryMB > minMemoryLimit) {
159-
retried = true
160147
memoryMB -= PROCESS_RUNTIME_MEMORY_STEP
161148
Log.i(tag, "Process memory limit failed, retrying with: $memoryMB")
162149
continue

app/src/main/java/app/morphe/manager/ui/model/PatcherStep.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ enum class State {
2424
WAITING, RUNNING, FAILED, COMPLETED
2525
}
2626

27-
enum class ProgressKey {
28-
DOWNLOAD
29-
}
30-
3127
interface StepProgressProvider {
3228
val downloadProgress: Pair<Long, Long?>?
3329
}
@@ -40,6 +36,5 @@ data class Step(
4036
/** [0, 1] Percentage of the total operation */
4137
val progressPercentage : Double,
4238
val state: State = State.WAITING,
43-
val message: String? = null,
44-
val progressKey: ProgressKey? = null
39+
val message: String? = null
4540
) : Parcelable

app/src/main/java/app/morphe/manager/ui/screen/home/HomeBottomActionBar.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package app.morphe.manager.ui.screen.home
22

3-
import android.annotation.SuppressLint
43
import android.view.HapticFeedbackConstants
54
import androidx.compose.animation.*
65
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -35,14 +34,13 @@ import app.morphe.manager.ui.screen.shared.MorpheDefaults
3534
*/
3635
@Composable
3736
fun HomeBottomActionBar(
37+
modifier: Modifier = Modifier,
3838
onBundlesClick: () -> Unit,
3939
onSettingsClick: () -> Unit,
4040
isExpertModeEnabled: Boolean = false,
4141
showSearchButton: Boolean = false,
4242
searchActive: Boolean = false,
43-
onSearchClick: () -> Unit = {},
44-
@SuppressLint("ModifierParameter")
45-
modifier: Modifier = Modifier
43+
onSearchClick: () -> Unit = {}
4644
) {
4745
// Show labels only when there are 2 buttons, buttons are wider so there's space
4846
val showLabels = !showSearchButton

app/src/main/java/app/morphe/manager/ui/screen/home/HomeDialogs.kt

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,9 @@ import androidx.compose.animation.AnimatedVisibility
1111
import androidx.compose.animation.core.tween
1212
import androidx.compose.animation.fadeIn
1313
import androidx.compose.animation.fadeOut
14-
import androidx.compose.foundation.clickable
14+
import androidx.compose.foundation.layout.*
1515
import androidx.compose.foundation.selection.selectable
1616
import androidx.compose.foundation.selection.selectableGroup
17-
import androidx.compose.ui.semantics.Role
18-
import androidx.compose.ui.semantics.contentDescription
19-
import androidx.compose.ui.semantics.semantics
20-
import androidx.compose.foundation.layout.*
2117
import androidx.compose.foundation.shape.CircleShape
2218
import androidx.compose.foundation.shape.RoundedCornerShape
2319
import androidx.compose.material.icons.Icons
@@ -26,17 +22,16 @@ import androidx.compose.material.icons.filled.Check
2622
import androidx.compose.material.icons.filled.Download
2723
import androidx.compose.material.icons.outlined.*
2824
import androidx.compose.material3.*
29-
import androidx.compose.runtime.Composable
30-
import androidx.compose.runtime.MutableState
31-
import androidx.compose.runtime.getValue
32-
import androidx.compose.runtime.remember
33-
import androidx.compose.runtime.rememberCoroutineScope
25+
import androidx.compose.runtime.*
3426
import androidx.compose.ui.Alignment
3527
import androidx.compose.ui.Modifier
3628
import androidx.compose.ui.graphics.Color
3729
import androidx.compose.ui.platform.LocalContext
3830
import androidx.compose.ui.platform.LocalUriHandler
3931
import androidx.compose.ui.res.stringResource
32+
import androidx.compose.ui.semantics.Role
33+
import androidx.compose.ui.semantics.contentDescription
34+
import androidx.compose.ui.semantics.semantics
4035
import androidx.compose.ui.text.AnnotatedString
4136
import androidx.compose.ui.text.font.FontFamily
4237
import androidx.compose.ui.text.font.FontWeight
@@ -57,10 +52,8 @@ import app.morphe.manager.util.RemoteAvatar
5752
import app.morphe.manager.util.htmlAnnotatedString
5853
import app.morphe.manager.util.toast
5954
import app.morphe.patcher.patch.AppTarget
60-
import kotlinx.coroutines.Dispatchers
6155
import kotlinx.coroutines.delay
6256
import kotlinx.coroutines.launch
63-
import kotlinx.coroutines.withContext
6457
import java.net.URI
6558

6659
/**
@@ -1192,8 +1185,8 @@ fun WrongPackageDialog(
11921185

11931186
/**
11941187
* Version list card where each row is tappable.
1195-
* The selected version gets a checkmark; the recommended version is labelled when not selected.
1196-
* Experimental versions are always labelled regardless of selection state.
1188+
* The selected version gets a checkmark; the recommended version is labeled when not selected.
1189+
* Experimental versions are always labeled regardless of selection state.
11971190
*/
11981191
@Composable
11991192
private fun SelectableVersionListCard(
@@ -1368,14 +1361,13 @@ private fun SelectableVersionListCard(
13681361

13691362
@Composable
13701363
private fun VersionListCard(
1364+
modifier: Modifier = Modifier,
13711365
versions: List<String>,
13721366
recommendedIndex: Int = 0,
13731367
isCompatible: Boolean = false,
13741368
showUnpatchedBadge: Boolean = false,
13751369
experimentalVersions: Set<String> = emptySet(),
1376-
descriptions: Map<String, String> = emptyMap(),
1377-
@SuppressLint("ModifierParameter")
1378-
modifier: Modifier = Modifier
1370+
descriptions: Map<String, String> = emptyMap()
13791371
) {
13801372
if (versions.isEmpty()) return
13811373

0 commit comments

Comments
 (0)