Skip to content
Closed
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
3 changes: 2 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2026 MeiYongAI
Copyright (c) 2026 krolchonok
Copyright (c) 2026 MeiYongAI (Toki modifications)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
227 changes: 167 additions & 60 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ android {
compileSdk = 37

defaultConfig {
applicationId = "io.github.meiyongai.toki"
applicationId = "com.toki.lsposed"
minSdk = 28
targetSdk = 35
versionCode = 1
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<permission android:name="io.github.meiyongai.toki.permission.READ_DIAGNOSTICS"
<permission android:name="com.toki.lsposed.permission.READ_DIAGNOSTICS"
android:protectionLevel="signature" />
<uses-permission android:name="io.github.meiyongai.toki.permission.READ_DIAGNOSTICS" />
<uses-permission android:name="com.toki.lsposed.permission.READ_DIAGNOSTICS" />

<!-- 声明目标宿主应用的包可见性,确保 Android 11+ (API 30+) 环境下可正确解析启动 Intent -->
<queries>
Expand Down Expand Up @@ -40,7 +40,7 @@
<!-- 桌面启动别名:禁用该组件即可从桌面隐藏模块图标,
恢复入口为 LSPosed 管理器中的模块启动按钮 -->
<activity-alias
android:name="io.github.meiyongai.toki.LauncherAlias"
android:name="com.toki.lsposed.LauncherAlias"
android:targetActivity=".ui.MainActivity"
android:exported="true">
<intent-filter>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/toki/lsposed/TokiApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.toki.lsposed
import android.app.Application
import com.toki.lsposed.provider.ConfigClient
import com.toki.lsposed.util.LSPosedStatusHelper
import com.toki.lsposed.ui.TikTokRootLogMonitor

/** 管理端进程入口,在界面创建前准备本地设置和框架发布通道。 */
class TokiApplication : Application() {
Expand All @@ -15,5 +16,7 @@ class TokiApplication : Application() {
super.onCreate()
ConfigClient.init(this)
LSPosedStatusHelper.init()
// Root 诊断在 Toki 管理端独立运行,不向 TikTok 注入监控线程。
TikTokRootLogMonitor.ensureStarted()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ object AuthorLocationHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主应用启动与前台初始化时同步。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主应用启动与前台初始化时同步。
*/
fun refreshConfig(context: Context) {
ConfigClient.init(context)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/AutoScrollHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ object AutoScrollHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主冷启动完成时分发。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主冷启动完成时分发。
*/
fun refreshConfig(context: Context) {
// 配置由 ConfigClient 统一管理并缓存
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/CommentCopyHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object CommentCopyHook {
* 初始化共享配置;每次复制直接读取最新快照,不另存功能开关。
* @param context 已附加的宿主上下文。
* @return Unit。
* Callers: TokiModule.hookApplication。
* Callers: TokiModule.hookApplicationIfNeeded。
*/
fun refreshConfig(context: Context) = ConfigClient.init(context)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import java.lang.reflect.Method
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.onPackageLoaded`: 宿主目标包加载就绪后初始化挂载。
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 目标应用初始化时同步开关配置。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 目标应用初始化时同步开关配置。
*/
object CommentTranslateHook {

Expand Down Expand Up @@ -58,7 +58,7 @@ object CommentTranslateHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 应用启动阶段同步配置。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 应用启动阶段同步配置。
*/
fun refreshConfig(context: Context) {
isEnabled = ConfigClient.getBoolean(context, KEY_COMMENT_TRANSLATE_ENABLED)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/DownloadHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ object DownloadHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主冷启动完成时分发。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主冷启动完成时分发。
*/
fun refreshConfig(context: Context) {
// 配置由 ConfigClient 统一管理并缓存
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/FeedFilterHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ object FeedFilterHook {
* 确保配置中心已初始化。
* @param context 已附加的宿主上下文。
* @return Unit。
* Callers: TokiModule.hookApplication。
* Callers: TokiModule.hookApplicationIfNeeded。
*/
fun refreshConfig(context: Context) = ConfigClient.init(context)

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/GpsHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object GpsHook {
* @param context 宿主目标应用的上下文对象。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主启动时触发配置加载。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主启动时触发配置加载。
*/
fun refreshConfig(context: Context) {
isEnabled = ConfigClient.getBoolean(context, "gps_spoof_enabled")
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/toki/lsposed/hook/HookRuntime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal object HookRuntime {
* 注册仅响应管理端查询的诊断接收器,不创建定时发布任务。
* @param value 已附加的宿主上下文。
* @return Unit。
* Callers: TokiModule.hookApplication。
* Callers: TokiModule.hookApplicationIfNeeded。
*/
@Synchronized fun attach(value: Context) {
if (receiver != null || process != value.packageName) return
Expand Down Expand Up @@ -163,7 +163,7 @@ internal object HookRuntime {
* @param feature 已注册的功能标识。
* @param configure 初始化或热更新操作。
* @return Unit。
* Callers: TokiModule.refreshConfiguration。
* Callers: TokiModule.refreshEnabledConfiguration。
*/
fun configure(feature: String, configure: () -> Unit) {
if (synchronized(this) { handles[feature].isNullOrEmpty() }) return
Expand Down
28 changes: 24 additions & 4 deletions app/src/main/java/com/toki/lsposed/hook/HostScanController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class HostScanController private constructor(private val app: Applicati
* 在宿主主进程订阅活动生命周期;进程级控制器独立调度退出,窗口随前台活动释放。
* @param app 当前宿主Application。
* @return Unit。
* Callers: TokiModule.hookApplication。
* Callers: TokiModule.hookApplicationIfNeeded。
*/
fun attach(app: Application) {
if (controller != null) return
Expand All @@ -39,6 +39,8 @@ internal class HostScanController private constructor(private val app: Applicati
private val main = Handler(Looper.getMainLooper())
private var foreground = WeakReference<Activity>(null)
private var dialog: HostScanDialog? = null
/** 扫描 UI 创建失败时只禁用 UI,不得影响宿主扫描及业务 Hook。 */
private var dialogCreationFailed = false
private val refresh = object : Runnable {
/** 更新可见窗口;无前台活动时停止刷新。@return Unit。Callers: 主线程Handler。 */
override fun run() {
Expand All @@ -58,10 +60,27 @@ internal class HostScanController private constructor(private val app: Applicati
scheduleClose()
val state = session.status
if (state.phase == HostScanPhase.IDLE) { dismissDialog(); return }
if (dialog == null) {
dialog = HostScanDialog(activity) { session.dismissFailure(); dismissDialog() }.also { it.show() }
if (dialog == null && !dialogCreationFailed) {
runCatching {
HostScanDialog(activity) { session.dismissFailure(); dismissDialog() }.also { it.show() }
}.onSuccess { created ->
dialog = created
HookRuntime.event("TokiHostScan", "扫描窗口已创建 pid=${Process.myPid()}")
}.onFailure { error ->
// 扫描窗口是诊断/适配 UI,不是宿主功能依赖。任何 UI/资源异常都必须被吞掉,
// 否则异常发生在主线程会直接终止 TikTok。后台 DEX 扫描继续运行。
dialogCreationFailed = true
Log.e("TokiHostScan", "扫描窗口创建失败,继续后台扫描,不影响 TikTok", error)
HookRuntime.failure("HostScanDialog", error, "扫描窗口不可用,已降级为后台扫描")
}
}

runCatching { dialog?.render(state) }.onFailure { error ->
dialogCreationFailed = true
Log.e("TokiHostScan", "扫描窗口刷新失败,继续后台扫描,不影响 TikTok", error)
HookRuntime.failure("HostScanDialog", error, "扫描窗口刷新失败,已降级为后台扫描")
dismissDialog()
}
dialog!!.render(state)
}

/**
Expand Down Expand Up @@ -105,6 +124,7 @@ internal class HostScanController private constructor(private val app: Applicati
/** 活动进入前台后恢复窗口。@param activity 宿主活动。@return Unit。Callers: Android Application。 */
override fun onActivityResumed(activity: Activity) {
dismissDialog()
dialogCreationFailed = false
foreground = WeakReference(activity)
main.post(refresh)
}
Expand Down
25 changes: 1 addition & 24 deletions app/src/main/java/com/toki/lsposed/hook/HostScanDialog.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.toki.lsposed.hook

import android.app.Activity
import android.content.ContextWrapper
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.Window
Expand Down Expand Up @@ -46,7 +44,7 @@ import com.toki.lsposed.ui.tokiColorScheme
* Callers: HostScanController.render。
*/
internal class HostScanDialog(activity: Activity, onFailureDismiss: () -> Unit) :
ComponentDialog(ScanContext(activity), android.R.style.Theme_Material_Light_Dialog_NoActionBar) {
ComponentDialog(activity, android.R.style.Theme_Material_Light_Dialog_NoActionBar) {
private var status by mutableStateOf(HostScanStatus())
private val composeView = ComposeView(context).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
Expand Down Expand Up @@ -93,27 +91,6 @@ internal class HostScanDialog(activity: Activity, onFailureDismiss: () -> Unit)
fun render(state: HostScanStatus) { status = state }
}

/**
* 保留宿主活动的窗口服务,隔离 Compose 读取的模块资源和主题。
* @param activity 宿主前台活动。
* Callers: HostScanDialog 构造函数。
*/
private class ScanContext(activity: Activity) : ContextWrapper(activity) {
private val moduleContext = activity.createPackageContext("io.github.meiyongai.toki", 0)
.createConfigurationContext(activity.resources.configuration)
private val moduleTheme = moduleContext.resources.newTheme().apply {
applyStyle(android.R.style.Theme_Material_Light_Dialog_NoActionBar, true)
}
/** 提供模块资源。@return Toki 资源集合。Callers: Compose、ComponentDialog。 */
override fun getResources(): Resources = moduleContext.resources
/** 提供模块资产。@return Toki 资产管理器。Callers: Android 字体与资源加载。 */
override fun getAssets(): android.content.res.AssetManager = moduleContext.assets
/** 提供与模块资源一致的主题。@return 窗口主题。Callers: ComponentDialog。 */
override fun getTheme(): Resources.Theme = moduleTheme
/** 解析模块 Compose 类型。@return 模块加载器。Callers: Android 视图系统。 */
override fun getClassLoader(): ClassLoader = checkNotNull(HostScanDialog::class.java.classLoader)
}

/**
* 渲染紧凑 Material 3 窗口;说明和方法区域内部滚动,进度与状态固定在底部。
* @param state 当前阶段和工作量。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object ImmersiveFullScreenHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主冷启动完成时分发。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主冷启动完成时分发。
*/
fun refreshConfig(context: Context) {
// 配置由 ConfigClient 统一管理并缓存
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/com/toki/lsposed/hook/LocaleHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import java.util.Locale
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.onPackageLoaded`: 宿主目标包加载就绪后初始化拦截器。
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主应用启动时刷新并应用配置。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主应用启动时刷新并应用配置。
*/
object LocaleHook {

Expand Down Expand Up @@ -88,7 +88,7 @@ object LocaleHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主启动时触发配置加载。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主启动时触发配置加载。
*/
fun refreshConfig(context: Context) {
isEnabled = ConfigClient.getBoolean(context, "language_spoof_enabled")
Expand Down Expand Up @@ -126,7 +126,7 @@ object LocaleHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主 Application 创建时调用。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主 Application 创建时调用。
*/
fun applyToApplication(app: Application) {
if (!isEnabled) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/MusicUnlockHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ object MusicUnlockHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主冷启动完成时分发。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主冷启动完成时分发。
*/
fun refreshConfig(context: Context) {
// 配置由 ConfigClient 统一管理并缓存
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ object PlaybackSpeedHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主进程初始化时同步。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主进程初始化时同步。
*/
fun refreshConfig(context: Context) {
if (speedMemory == null) speedMemory = PlaybackSpeedMemory(
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/SimHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ object SimHook {
* @param context 宿主目标应用的上下文对象。
*
* Callers:
* - `com.toki.lsposed.hook.SimHook.hookApplication`: 宿主 Application 创建时触发同步。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主 Application 创建时触发同步。
*/
fun refreshConfig(context: Context) {
isEnabled = ConfigClient.getBoolean(context, "sim_spoof_enabled")
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/toki/lsposed/hook/StatusBarHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ object StatusBarHook {
* Unit: 无返回值。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主冷启动完成时分发。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主冷启动完成时分发。
*/
fun refreshConfig(context: Context) {
// 配置由 ConfigClient 统一管理与跨进程分发
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/toki/lsposed/hook/TimeZoneHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object TimeZoneHook {
* @param context 宿主目标应用的上下文对象。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主启动时触发配置加载。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主启动时触发配置加载。
*/
fun refreshConfig(context: Context) {
isEnabled = ConfigClient.getBoolean(context, "timezone_spoof_enabled")
Expand Down Expand Up @@ -78,7 +78,7 @@ object TimeZoneHook {
* @param app 宿主 Application 实例。
*
* Callers:
* - `com.toki.lsposed.hook.TokiModule.hookApplication`: 宿主 Application 创建时注入生效。
* - `com.toki.lsposed.hook.TokiModule.hookApplicationIfNeeded`: 宿主 Application 创建时注入生效。
*/
fun applyToApplication(app: Application) {
if (!isEnabled) {
Expand Down
Loading