Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ data class InstalledApp(
@ColumnInfo(name = "selection_payload") val selectionPayload: SelectionPayload? = null,
@ColumnInfo(name = "patched_at") val patchedAt: Long? = null
)

/**
* Root mount binds the patched APK over the stock one, so it only applies while patching kept
* the original package name. Apps renamed by patches (GmsCore builds) must use a regular install.
*/
val InstalledApp.supportsMount: Boolean
get() = currentPackageName == originalPackageName
2 changes: 2 additions & 0 deletions app/src/main/java/app/morphe/manager/ui/screen/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.morphe.manager.R
import app.morphe.manager.data.room.apps.installed.supportsMount
import app.morphe.manager.domain.manager.HomeAppButtonPreferences
import app.morphe.manager.domain.manager.HomeAppCategoryState
import app.morphe.manager.domain.manager.HomeAppCategoryViewMode
Expand Down Expand Up @@ -164,6 +165,7 @@ fun HomeScreen(
InstallQueueRequest(
file = savedFile,
originalPackageName = installed.originalPackageName,
mountPackageName = installed.currentPackageName.takeIf { installed.supportsMount },
onPersistApp = { packageName, installType ->
homeViewModel.persistReinstalledApp(installed, packageName, installType)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.morphe.manager.R
import app.morphe.manager.data.room.apps.installed.InstallType
import app.morphe.manager.data.room.apps.installed.InstalledApp
import app.morphe.manager.data.room.apps.installed.supportsMount
import app.morphe.manager.patcher.patch.PatchInfo
import app.morphe.manager.patcher.util.NativeLibStripper
import app.morphe.manager.ui.screen.settings.system.InstallerSelectionDialog
Expand Down Expand Up @@ -1197,6 +1198,35 @@ private fun ActionsSection(
)
}

val installsThroughMount = viewModel.primaryInstallerIsMount && installedApp.supportsMount

val mountSavedApp: () -> Unit = {
val savedFile = viewModel.savedApkFile()
if (savedFile != null) {
installViewModel.installSavedMount(
outputFile = savedFile,
packageName = installedApp.currentPackageName,
onPersistApp = { _, _ ->
viewModel.updateInstallType(
packageName = installedApp.currentPackageName,
newInstallType = InstallType.MOUNT
)
true
}
)
} else if (viewModel.isMounted) {
installViewModel.remount(
packageName = installedApp.currentPackageName,
version = installedApp.version
)
} else {
installViewModel.mount(
packageName = installedApp.currentPackageName,
version = installedApp.version
)
}
}

// Show install/reinstall from saved copy whenever the patched APK is available
if (viewModel.hasSavedCopy) {
val installText = if (viewModel.isInstalledOnDevice) {
Expand All @@ -1212,15 +1242,19 @@ private fun ActionsSection(
val savedFile = viewModel.savedApkFile()
if (savedFile != null) {
val installAction = {
installViewModel.install(
outputFile = savedFile,
originalPackageName = installedApp.originalPackageName,
onPersistApp = { _, _ ->
// Callback will be called after successful installation
// The LaunchedEffect handler will update the installation type
true
}
)
if (installsThroughMount) {
mountSavedApp()
} else {
installViewModel.install(
outputFile = savedFile,
originalPackageName = installedApp.originalPackageName,
onPersistApp = { _, _ ->
// Callback will be called after successful installation
// The LaunchedEffect handler will update the installation type
true
}
)
}
}

// Check if mount warning is needed
Expand All @@ -1244,32 +1278,6 @@ private fun ActionsSection(
when (installedApp.installType) {
InstallType.MOUNT -> {
val isMountLoading = mountOperation != null
val mountSavedApp: () -> Unit = {
val savedFile = viewModel.savedApkFile()
if (savedFile != null) {
installViewModel.installSavedMount(
outputFile = savedFile,
packageName = installedApp.currentPackageName,
onPersistApp = { _, _ ->
viewModel.updateInstallType(
packageName = installedApp.currentPackageName,
newInstallType = InstallType.MOUNT
)
true
}
)
} else if (viewModel.isMounted) {
installViewModel.remount(
packageName = installedApp.currentPackageName,
version = installedApp.version
)
} else {
installViewModel.mount(
packageName = installedApp.currentPackageName,
version = installedApp.version
)
}
}
if (viewModel.isMounted) {
// Remount button
secondaryActions.add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import app.morphe.manager.R
import app.morphe.manager.data.platform.Filesystem
import app.morphe.manager.data.room.apps.installed.InstallType
import app.morphe.manager.data.room.apps.installed.InstalledApp
import app.morphe.manager.data.room.apps.installed.supportsMount
import app.morphe.manager.data.room.apps.original.OriginalApk
import app.morphe.manager.domain.installer.InstallerFileProvider
import app.morphe.manager.domain.installer.InstallerManager
Expand Down Expand Up @@ -263,10 +264,10 @@ private fun PatchedApksContent(
fun installRequests(items: List<ApkItemData>) = items.mapNotNull { item ->
val installedApp = appByKey[item.selectionKey] ?: return@mapNotNull null
val file = item.file?.takeIf { it.exists() } ?: return@mapNotNull null
if (item.installType == InstallType.MOUNT) return@mapNotNull null
InstallQueueRequest(
file = file,
originalPackageName = installedApp.originalPackageName,
mountPackageName = installedApp.currentPackageName.takeIf { installedApp.supportsMount },
onPersistApp = { packageName, installType ->
val appliedPatches = repository.getAppliedPatches(installedApp.currentPackageName)
repository.addOrUpdate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.morphe.manager.R
import app.morphe.manager.data.room.apps.installed.InstallType
import app.morphe.manager.domain.installer.InstallerManager
import app.morphe.manager.ui.viewmodel.InstallViewModel
import app.morphe.manager.util.batchActionSummary
import app.morphe.manager.util.toast
Expand All @@ -25,14 +26,18 @@ import java.io.File
/**
* A single install request queued by [rememberInstallQueue].
*
* @param onPersistApp forwarded to [InstallViewModel.install]; runs after a successful install
* to persist app metadata (patch selection, install type) in the caller's repository.
* @param mountPackageName package to mount when this request targets a saved patched APK and
* Mount is the primary installer. Null for apps that patching renamed, since mount
* replaces the stock APK in place and cannot serve a different package.
* @param onPersistApp forwarded to [InstallViewModel.install] or [InstallViewModel.installSavedMount];
* runs after a successful install to persist app metadata in the caller's repository.
* @param onInstalled invoked with the installed package name after a successful install,
* before the next queue item starts.
*/
data class InstallQueueRequest(
val file: File,
val originalPackageName: String,
val mountPackageName: String? = null,
val onPersistApp: suspend (String, InstallType) -> Boolean,
val onInstalled: (installedPackageName: String) -> Unit = {}
)
Expand Down Expand Up @@ -88,11 +93,23 @@ fun rememberInstallQueue(

active = next
activeStarted = true
installViewModel.install(
outputFile = file,
originalPackageName = next.originalPackageName,
onPersistApp = next.onPersistApp
)
val mountPackageName = next.mountPackageName
if (
mountPackageName != null &&
installViewModel.getPrimaryInstallerToken() == InstallerManager.Token.AutoSaved
) {
installViewModel.installSavedMount(
outputFile = file,
packageName = mountPackageName,
onPersistApp = next.onPersistApp
)
} else {
installViewModel.install(
outputFile = file,
originalPackageName = next.originalPackageName,
onPersistApp = next.onPersistApp
)
}
}

LaunchedEffect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ class InstallViewModel : ViewModel(), KoinComponent {
if (installState is InstallState.Installing) return

viewModelScope.launch {
currentInstallType = InstallType.MOUNT
installState = InstallState.Installing

try {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ Outer zone (dashed): may be clipped by the launcher mask - keep important detail
<string name="root_pre_patch_installer_description">Your device has root access. Choose whether this app should be patched for root mount or standard install</string>
<string name="root_pre_patch_installer_mount_title">Root mount mode</string>
<string name="root_pre_patch_installer_mount_description">Patches for root mount, then mounts the APK over the matching stock app. No GmsCore needed</string>
<string name="root_mount_module_unmount_warning">If you use KernelSU or APatch, disable module unmounting or hiding for apps installed with root mount. Otherwise the stock app may still run even when Morphe shows it as mounted</string>
<string name="root_mount_module_unmount_warning">If KernelSU has Unmount modules by default enabled, open KernelSU > Superuser > the app you mounted > Profile > Custom and disable Unmount modules. Otherwise the mounted patched APK may not take effect, even if Morphe shows it as mounted</string>
<string name="root_pre_patch_installer_standard_title">Standard install mode</string>
<string name="root_pre_patch_installer_standard_description">Patches as a normal APK with GmsCore support. Your installer setting is used after patching</string>
<string name="root_pre_patch_installer_hint">This patch mode is separate from Settings > System > Installer and cannot be changed after patching</string>
Expand Down
Loading