Skip to content
35 changes: 35 additions & 0 deletions app/src/main/java/btm/m/os4/systemuihook/HmcConflictDetector.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
package btm.m.os4.systemuihook

import android.content.Context
import android.content.pm.PackageManager
import java.util.Locale

/**
* Best-effort detection for installed standalone HyperMusicCover/HMC forks.
*
* Android does not expose a reliable public API for asking whether an arbitrary
* LSPosed module is enabled. This detector therefore checks installed app
* identity (package name + visible application label), not LSPosed activation.
*/
internal object HmcConflictDetector {
private val hmcVariantPattern =
Regex("""(?i)(?<![a-z0-9])hmc(?:[\s._-]*(?:e|plus|mod|fork|beta|alpha|pro|lite))?(?![a-z0-9])""")

fun isHmcVariantInstalled(context: Context): Boolean = runCatching {
val pm = context.packageManager
val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)
apps.any { app ->
if (app.packageName == context.packageName) {
false
} else {
val label = runCatching { app.loadLabel(pm).toString() }.getOrDefault("")
val identity = "${app.packageName} $label"
val compact = identity.lowercase(Locale.ROOT).filter { it.isLetterOrDigit() }
val hasFullName = compact.contains("hypermusiccover")
val hasHmcVariant = hmcVariantPattern.containsMatchIn(identity)
hasFullName || hasHmcVariant
}
}
}.getOrDefault(false)
}
17 changes: 17 additions & 0 deletions app/src/main/java/btm/m/os4/systemuihook/HyperSystemUiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1589,11 +1589,21 @@ class HyperSystemUiModule : XposedModule() {
}
}

private fun isStandaloneHyperMusicCoverInstalled(context: Context): Boolean =
HmcConflictDetector.isHmcVariantInstalled(context)

private fun sendHyperMusicCoverMediaCardCommand(
context: Context,
hideArtwork: Boolean,
centerText: Boolean,
) {
if (isStandaloneHyperMusicCoverInstalled(context)) {
btm.m.os4.systemuihook.HookModuleLogger.log(
this, Log.INFO, TAG,
"Standalone HyperMusicCover detected; skipping HyperChanger media-card command",
)
return
}
runCatching {
context.sendBroadcast(
Intent(HYPER_MUSIC_COVER_ACTION)
Expand All @@ -1608,6 +1618,13 @@ class HyperSystemUiModule : XposedModule() {
}

private fun sendHyperMusicCoverCommand(context: Context, op: String, on: Boolean) {
if (isStandaloneHyperMusicCoverInstalled(context)) {
btm.m.os4.systemuihook.HookModuleLogger.log(
this, Log.INFO, TAG,
"Standalone HyperMusicCover detected; skipping HyperChanger command op=$op on=$on",
)
return
}
runCatching {
context.sendBroadcast(
Intent(HYPER_MUSIC_COVER_ACTION)
Expand Down
23 changes: 21 additions & 2 deletions app/src/main/java/btm/m/os4/systemuihook/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,9 @@ private fun suggestedScopesForHookChange(before: HookSettings, after: HookSettin
return scopes
}

private fun isStandaloneHyperMusicCoverInstalled(context: Context): Boolean =
HmcConflictDetector.isHmcVariantInstalled(context)

@Composable
private fun Root(
hooks: HookSettingsStore,
Expand Down Expand Up @@ -8721,6 +8724,14 @@ private fun Lock(
),
) { p, scroll ->
val context = LocalContext.current
val standaloneHmcInstalled = remember(context) { isStandaloneHyperMusicCoverInstalled(context) }
LaunchedEffect(standaloneHmcInstalled, s.lockscreenMusicLockscreenEnabled) {
if (standaloneHmcInstalled && s.lockscreenMusicLockscreenEnabled) {
// HMC and HC both hook SystemUI. Disable HC's stored toggle when the standalone
// module is installed so it cannot silently reactivate after a later restart.
update { it.copy(lockscreenMusicLockscreenEnabled = false) }
}
}
val shortcutGestureStore = remember(context) { LockscreenShortcutBackgroundSettingsStore(context) }
val shortcutGestureService by HookApplication.service.collectAsStateWithLifecycle()
var shortcutGestureMode by remember { mutableIntStateOf(shortcutGestureStore.settings.gestureMode) }
Expand Down Expand Up @@ -9088,9 +9099,17 @@ OverlayDropdownPreference(
"\u9700\u8981\u540c\u65f6\u91cd\u542f\u201c\u7cfb\u7edf\u754c\u9762\u201d\u548c\u201c\u58c1\u7eb8\u201d\u5e94\u7528",
"\u9700\u8981\u540c\u65f6\u91cd\u542f\u201c\u7cfb\u7edf\u754c\u9762\u201d\u548c\u201c\u58c1\u7eb8\u201d\u5e94\u7528",
),
checked = s.lockscreenMusicLockscreenEnabled,
checked = s.lockscreenMusicLockscreenEnabled && !isStandaloneHyperMusicCoverInstalled(context),
onCheckedChange = { value ->
update { it.copy(lockscreenMusicLockscreenEnabled = value) }
if (value && isStandaloneHyperMusicCoverInstalled(context)) {
Toast.makeText(
context,
"检测到独立版 HyperMusicCover 已安装。为避免两个模块同时 Hook SystemUI,请先在 LSPosed 中停用 HMC,再开启 HC 音乐锁屏。",
Toast.LENGTH_LONG,
).show()
} else {
update { it.copy(lockscreenMusicLockscreenEnabled = value) }
}
},
)
AnimatedVisibility(
Expand Down
Loading
Loading