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
1 change: 1 addition & 0 deletions app/src/main/java/io/github/soclear/oneuix/data/Package.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ object Package {
const val THEME_CENTER = "com.samsung.android.themecenter"
const val WEATHER = "com.sec.android.daemonapp"
const val VIDEO = "com.samsung.android.video"
const val WATCH_MANAGER = "com.samsung.android.app.watchmanager"
}
3 changes: 3 additions & 0 deletions app/src/main/java/io/github/soclear/oneuix/data/Preference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ data class Preference(
val bypassHealthMonitorCountryCheck: Boolean = false,
val useSPenGoogleTranslate: Boolean = false,
val hideAppsSearchBar: Boolean = false,
val bypassWatchPairingRegionCheck: Boolean = false,
val watchPairingConnectionMode: Int = 0,
val supplementChinaWearOsGms: Boolean = false,
)

companion object {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/io/github/soclear/oneuix/hook/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,19 @@ class Main : IXposedHookLoadPackage, IXposedHookInitPackageResources {
}
}

Package.WATCH_MANAGER -> {
if (preference.other.bypassWatchPairingRegionCheck ||
preference.other.watchPairingConnectionMode != WatchPairing.MODE_NONE
) {
WatchPairing.init(
lpparam = lpparam,
bypassRegionCheck = preference.other.bypassWatchPairingRegionCheck,
connectionMode = preference.other.watchPairingConnectionMode,
supplementChinaWearOsGms = preference.other.supplementChinaWearOsGms
)
}
}

"com.samsung.android.service.airviewdictionary" -> {
if (preference.other.useSPenGoogleTranslate) {
SPen.switchTranslateSource(lpparam, useGoogle = true)
Expand Down
165 changes: 165 additions & 0 deletions app/src/main/java/io/github/soclear/oneuix/hook/WatchPairing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package io.github.soclear.oneuix.hook

import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
import io.github.soclear.oneuix.data.Package

object WatchPairing {
const val MODE_NONE = 0
const val MODE_WEAROS_CN = 1
const val MODE_WEAROS_GLOBAL = 2

fun init(
lpparam: LoadPackageParam,
bypassRegionCheck: Boolean,
connectionMode: Int,
supplementChinaWearOsGms: Boolean,
) {
if (lpparam.packageName != Package.WATCH_MANAGER) return

XposedBridge.log("OneUIX: WatchPairing init: bypassRegionCheck=$bypassRegionCheck, connectionMode=$connectionMode")

if (bypassRegionCheck) {
bypassRegionCheck(lpparam)
disableCscCheck(lpparam)
bypassPairingProblemCheck(lpparam)
spoofCscValue(lpparam)
}

if (connectionMode != MODE_NONE) {
spoofChinaEdition(lpparam, connectionMode)
}

if (connectionMode == MODE_WEAROS_CN && supplementChinaWearOsGms) {
supplementChinaWearOsGms(lpparam)
}
}

private fun bypassRegionCheck(lpparam: LoadPackageParam) {
try {
XposedHelpers.findAndHookMethod(
"com.samsung.android.app.twatchmanager.connectionmanager.util.BluetoothUuidUtil",
lpparam.classLoader,
"checkDeviceRegion",
android.content.Context::class.java,
android.bluetooth.BluetoothDevice::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
param.result = false
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}

private fun spoofChinaEdition(lpparam: LoadPackageParam, connectionMode: Int) {
try {
XposedHelpers.findAndHookMethod(
"com.samsung.android.app.global.utils.GoogleRequirementUtils",
lpparam.classLoader,
"isChinaEdition",
android.content.Context::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
when (connectionMode) {
MODE_WEAROS_CN -> param.result = true
MODE_WEAROS_GLOBAL -> param.result = false
}
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}

private fun disableCscCheck(lpparam: LoadPackageParam) {
try {
XposedHelpers.findAndHookMethod(
"com.samsung.android.app.global.utils.PlatformUtils",
lpparam.classLoader,
"isSamsungChinaModel",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Any {
return false
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}

private fun bypassPairingProblemCheck(lpparam: LoadPackageParam) {
try {
val problemClass = XposedHelpers.findClass(
"com.samsung.android.app.watchmanager.setupwizard.pairing.PairingProblemChecker\$Problem",
lpparam.classLoader
)

XposedHelpers.findAndHookMethod(
"com.samsung.android.app.watchmanager.setupwizard.pairing.PairingProblemChecker",
lpparam.classLoader,
"problemCheckAfterPairing",
"com.samsung.android.app.twatchmanager.connectionmanager.define.WearableDevice",
"android.bluetooth.BluetoothDevice",
Boolean::class.javaPrimitiveType,
"androidx.fragment.app.FragmentActivity",
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
if (param.result?.toString() == "WEAR_OS_NOT_SUPPORTED_PHONE") {
param.result = XposedHelpers.getStaticObjectField(
problemClass,
"NO_PROBLEM"
)
}
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}

private fun supplementChinaWearOsGms(lpparam: LoadPackageParam) {
try {
XposedHelpers.findAndHookMethod(
"com.samsung.android.app.watchmanager.setupwizard.downloadinstall.HMConnectFragment",
lpparam.classLoader,
"makePackageListToDownload",
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
val packages = param.result as? Set<*> ?: return
val newPackages = packages.toMutableSet()
newPackages.add("com.google.android.wearable.app.cn")
param.result = newPackages
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}

private fun spoofCscValue(lpparam: LoadPackageParam) {
try {
XposedHelpers.findAndHookMethod(
"com.samsung.android.app.twatchmanager.util.PlatformNetworkUtils",
lpparam.classLoader,
"getCSC",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Any {
return "TGY"
}
}
)
} catch (t: Throwable) {
XposedBridge.log(t)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import androidx.compose.ui.res.vectorResource
import io.github.soclear.oneuix.R
import io.github.soclear.oneuix.data.Preference
import io.github.soclear.oneuix.ui.SettingViewModel
import io.github.soclear.oneuix.ui.component.SelectItem
import io.github.soclear.oneuix.ui.component.SwitchItem

private const val WATCH_PAIRING_MODE_CN = 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This constant is a duplicate of WatchPairing.MODE_WEAROS_CN. To improve maintainability and ensure consistency between the UI and the hook logic, these constants should be moved to a shared location, such as a companion object in Preference.kt or a dedicated constants file.


@Composable
fun DetailPaneOther(
uiState: Preference.Other,
Expand Down Expand Up @@ -125,6 +128,34 @@ fun DetailPaneOther(
checked = uiState.hideAppsSearchBar,
onCheckedChange = { onEvent(OtherEvent.HideAppsSearchBar(it)) }
)
SelectItem(
icon = ImageVector.vectorResource(id = R.drawable.watch_pairing),
title = stringResource(id = R.string.watchPairing_connectionMode_title),
summary = stringResource(id = R.string.watchPairing_connectionMode_summary),
entries = listOf(
stringResource(id = R.string.watchPairing_mode_none),
stringResource(id = R.string.watchPairing_mode_wearos_cn),
stringResource(id = R.string.watchPairing_mode_wearos_global)
),
selectedIndex = uiState.watchPairingConnectionMode,
onSelectedIndexChange = { onEvent(OtherEvent.WatchPairingConnectionMode(it)) }
)
SwitchItem(
icon = ImageVector.vectorResource(id = R.drawable.lock_open),
title = stringResource(id = R.string.bypassWatchPairingRegionCheck_title),
summary = stringResource(id = R.string.bypassWatchPairingRegionCheck_summary),
checked = uiState.bypassWatchPairingRegionCheck,
onCheckedChange = { onEvent(OtherEvent.BypassWatchPairingRegionCheck(it)) }
)
if (uiState.watchPairingConnectionMode == WATCH_PAIRING_MODE_CN) {
SwitchItem(
icon = ImageVector.vectorResource(id = R.drawable.google_play),
title = stringResource(id = R.string.supplementChinaWearOsGms_title),
summary = stringResource(id = R.string.supplementChinaWearOsGms_summary),
checked = uiState.supplementChinaWearOsGms,
onCheckedChange = { onEvent(OtherEvent.SupplementChinaWearOsGms(it)) }
)
}
}
}

Expand Down Expand Up @@ -174,6 +205,15 @@ sealed interface OtherEvent {

@JvmInline
value class HideAppsSearchBar(val value: Boolean) : OtherEvent

@JvmInline
value class BypassWatchPairingRegionCheck(val value: Boolean) : OtherEvent

@JvmInline
value class WatchPairingConnectionMode(val value: Int) : OtherEvent

@JvmInline
value class SupplementChinaWearOsGms(val value: Boolean) : OtherEvent
}

fun SettingViewModel.onOtherEvent(event: OtherEvent) {
Expand Down Expand Up @@ -268,6 +308,29 @@ fun SettingViewModel.onOtherEvent(event: OtherEvent) {
hideAppsSearchBar = event.value
)
)

is OtherEvent.BypassWatchPairingRegionCheck -> preference.copy(
other = preference.other.copy(
bypassWatchPairingRegionCheck = event.value
)
)

is OtherEvent.WatchPairingConnectionMode -> preference.copy(
other = preference.other.copy(
watchPairingConnectionMode = event.value,
supplementChinaWearOsGms = if (event.value == WATCH_PAIRING_MODE_CN) {
preference.other.supplementChinaWearOsGms
} else {
false
}
)
)

is OtherEvent.SupplementChinaWearOsGms -> preference.copy(
other = preference.other.copy(
supplementChinaWearOsGms = event.value
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.github.soclear.oneuix.ui.component

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp

@Composable
fun SelectItem(
title: String,
modifier: Modifier = Modifier,
summary: String? = null,
icon: ImageVector? = null,
entries: List<String>,
selectedIndex: Int,
onSelectedIndexChange: (Int) -> Unit
) {
var expanded by remember { mutableStateOf(false) }

Box(modifier = modifier) {
ListItem(
headlineContent = { Text(title) },
supportingContent = {
Text(entries.getOrElse(selectedIndex) { summary ?: "" })
},
leadingContent = icon?.let { { Icon(it, title) } },
modifier = Modifier
.fillMaxWidth()
.clickable { expanded = true }
)

DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.widthIn(min = 200.dp, max = 320.dp)
) {
entries.forEachIndexed { index, label ->
DropdownMenuItem(
text = { Text(label) },
onClick = {
onSelectedIndexChange(index)
expanded = false
}
)
}
}
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/google_play.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M3,20.5v-17c0,-0.83 0.95,-1.3 1.6,-0.8l10.8,8.5c0.52,0.41 0.52,1.19 0,1.6L4.6,21.3C3.95,21.8 3,21.33 3,20.5zM16.2,13.72l2.45,1.93c0.62,0.49 1.35,0.04 1.35,-0.76v-5.78c0,-0.8 -0.73,-1.25 -1.35,-0.76l-2.45,1.93c0.58,0.93 0.58,2.51 0,3.44z" />
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/lock_open.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#1f1f1f"
android:pathData="M240,800h480L720,400L240,400v400zM536.5,656.5Q560,633 560,600t-23.5,-56.5Q513,520 480,520t-56.5,23.5Q400,567 400,600t23.5,56.5Q447,680 480,680t56.5,-23.5zM240,880q-33,0 -56.5,-23.5T160,800L160,400q0,-33 23.5,-56.5T240,320h280v-80q0,-83 58.5,-141.5T720,40q83,0 141.5,58.5T920,240h-80q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85v80h120q33,0 56.5,23.5T800,400v400q0,33 -23.5,56.5T720,880L240,880z" />
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/watch_pairing.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#1f1f1f"
android:pathData="M360,880l-54,-182q-48,-38 -77,-95t-29,-123q0,-66 29,-123t77,-95l54,-182h240l54,182q48,38 77,95t29,123q0,66 -29,123t-77,95l-54,182L360,880zM621.5,621.5Q680,563 680,480t-58.5,-141.5Q563,280 480,280t-141.5,58.5Q280,397 280,480t58.5,141.5Q397,680 480,680t141.5,-58.5zM404,210q20,-5 38.5,-8t37.5,-3q19,0 37.5,3t38.5,8l-16,-50L420,160l-16,50zM420,800h120l16,-50q-20,5 -38.5,7.5T480,760q-19,0 -37.5,-2.5T404,750l16,50z" />
</vector>
Loading