Skip to content

Commit ff1a495

Browse files
committed
feat: settings
1 parent e1863d6 commit ff1a495

25 files changed

+1022
-113
lines changed

app/build.gradle.kts

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.lsplugin.jgit)
55
alias(libs.plugins.lsplugin.apksign)
6+
alias(libs.plugins.kotlin.compose)
67
}
78

89
apksign {
@@ -56,10 +57,24 @@ android {
5657
kotlinOptions {
5758
jvmTarget = JavaVersion.VERSION_17.toString()
5859
}
60+
buildFeatures {
61+
compose = true
62+
buildConfig = true
63+
}
5964
}
6065

6166
dependencies {
67+
implementation(libs.lifecycle.runtime.ktx)
68+
implementation(libs.activity.compose)
69+
implementation(platform(libs.compose.bom))
70+
implementation(libs.material3)
71+
implementation(libs.animation.core.android)
72+
implementation(libs.animation.android)
73+
implementation(libs.androidx.ui)
74+
implementation(libs.androidx.ui.graphics)
75+
implementation(libs.accompanist.drawablepainter)
6276
compileOnly(project(":libxposed-compat"))
6377
compileOnly(libs.libxposed.api)
78+
implementation(libs.libxposed.service)
6479
implementation(libs.hiddenapibypass)
6580
}

app/src/main/AndroidManifest.xml

+26-6
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,45 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33

44
<application
5+
android:name=".MainApplication"
56
android:description="@string/xposed_description"
6-
android:label="@string/app_name"
77
android:icon="@mipmap/ic_launcher"
8-
android:supportsRtl="true"
9-
android:theme="@android:style/Theme.Translucent.NoTitleBar">
8+
android:label="@string/app_name"
9+
android:supportsRtl="true">
10+
<activity
11+
android:name=".ui.activity.SettingsActivity"
12+
android:exported="true"
13+
android:label="@string/title_activity_settings"
14+
android:noHistory="true" >
15+
<intent-filter>
16+
<action android:name="android.intent.action.MAIN" />
17+
18+
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
19+
</intent-filter>
20+
<intent-filter>
21+
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
22+
</intent-filter>
23+
</activity>
1024
<activity
11-
android:name=".MainActivity"
12-
android:exported="true">
25+
android:name=".ui.activity.MainActivity"
26+
android:exported="true"
27+
android:theme="@android:style/Theme.Translucent.NoTitleBar">
1328
<intent-filter>
1429
<action android:name="android.intent.action.MAIN" />
1530

1631
<category android:name="android.intent.category.LAUNCHER" />
1732
</intent-filter>
33+
34+
<meta-data
35+
android:name="android.app.shortcuts"
36+
android:resource="@xml/shortcuts" />
1837
</activity>
38+
1939
<service
2040
android:name=".LaunchTileService"
2141
android:exported="true"
22-
android:label="@string/tile_label"
2342
android:icon="@drawable/ic_tile_service"
43+
android:label="@string/tile_label"
2444
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
2545
<intent-filter>
2646
<action android:name="android.service.quicksettings.action.QS_TILE" />

app/src/main/java/com/parallelc/micts/LaunchTileService.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import android.app.PendingIntent
55
import android.content.Intent
66
import android.os.Build
77
import android.service.quicksettings.TileService
8+
import com.parallelc.micts.ui.activity.MainActivity
89

910
class LaunchTileService : TileService() {
10-
@SuppressLint("PrivateApi", "StartActivityAndCollapseDeprecated")
11+
@SuppressLint("StartActivityAndCollapseDeprecated")
1112
override fun onClick() {
1213
super.onClick()
1314

app/src/main/java/com/parallelc/micts/MainActivity.kt

-36
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.parallelc.micts
2+
3+
import android.app.Application
4+
import androidx.lifecycle.ViewModelProvider
5+
import com.parallelc.micts.ui.viewmodel.SettingsViewModel
6+
7+
class MainApplication : Application() {
8+
val settingsViewModel: SettingsViewModel by lazy {
9+
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
10+
.create(SettingsViewModel::class.java)
11+
}
12+
}

app/src/main/java/com/parallelc/micts/ModuleMain.kt

+19-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ package com.parallelc.micts
22

33
import android.content.Context
44
import android.os.Build
5+
import com.parallelc.micts.config.TriggerService
6+
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
7+
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
8+
import com.parallelc.micts.config.XposedConfig.KEY_DEVICE_SPOOF
9+
import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
10+
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_BRAND
11+
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_DEVICE
12+
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_MANUFACTURER
13+
import com.parallelc.micts.config.XposedConfig.KEY_SPOOF_MODEL
514
import com.parallelc.micts.hooker.InvokeOmniHooker
615
import com.parallelc.micts.hooker.LongPressHomeHooker
716
import com.parallelc.micts.hooker.NavStubViewHooker
@@ -12,7 +21,7 @@ import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
1221
import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
1322
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
1423

15-
lateinit var module: ModuleMain
24+
var module: ModuleMain? = null
1625

1726
class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule(base, param) {
1827

@@ -23,7 +32,7 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
2332
override fun onSystemServerLoaded(param: SystemServerLoadedParam) {
2433
super.onSystemServerLoaded(param)
2534

26-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
35+
if (TriggerService.getSupportedServices().contains(TriggerService.CSHelper)) {
2736
runCatching {
2837
VIMSHooker.hook(param)
2938
}.onFailure { e ->
@@ -43,8 +52,11 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
4352
super.onPackageLoaded(param)
4453
if (!param.isFirstPackage) return
4554

55+
val prefs = getRemotePreferences(CONFIG_NAME)
56+
4657
when (param.packageName) {
4758
"com.miui.home", "com.mi.android.globallauncher" -> {
59+
if (!prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) return
4860
runCatching {
4961
val circleToSearchHelper = param.classLoader.loadClass("com.miui.home.recents.cts.CircleToSearchHelper")
5062
hook(circleToSearchHelper.getDeclaredMethod("invokeOmni", Context::class.java, Int::class.java, Int::class.java), InvokeOmniHooker::class.java)
@@ -60,19 +72,20 @@ class ModuleMain(base: XposedInterface, param: ModuleLoadedParam) : XposedModule
6072
}
6173
}
6274
"com.google.android.googlequicksearchbox" -> {
75+
if (!prefs.getBoolean(KEY_DEVICE_SPOOF, DEFAULT_CONFIG[KEY_DEVICE_SPOOF] as Boolean)) return
6376
val buildClass = param.classLoader.loadClass("android.os.Build")
6477
val MANUFACTURER = buildClass.getDeclaredField("MANUFACTURER")
6578
MANUFACTURER.isAccessible = true
66-
MANUFACTURER.set(null, "Google")
79+
MANUFACTURER.set(null, prefs.getString(KEY_SPOOF_MANUFACTURER, DEFAULT_CONFIG[KEY_SPOOF_MANUFACTURER] as String))
6780
val BRAND = buildClass.getDeclaredField("BRAND")
6881
BRAND.isAccessible = true
69-
BRAND.set(null, "google")
82+
BRAND.set(null, prefs.getString(KEY_SPOOF_BRAND, DEFAULT_CONFIG[KEY_SPOOF_BRAND] as String))
7083
val MODEL = buildClass.getDeclaredField("MODEL")
7184
MODEL.isAccessible = true
72-
MODEL.set(null, "Pixel 8 Pro")
85+
MODEL.set(null, prefs.getString(KEY_SPOOF_MODEL, DEFAULT_CONFIG[KEY_SPOOF_MODEL] as String))
7386
val DEVICE = buildClass.getDeclaredField("DEVICE")
7487
DEVICE.isAccessible = true
75-
DEVICE.set(null, "husky")
88+
DEVICE.set(null, prefs.getString(KEY_SPOOF_DEVICE, DEFAULT_CONFIG[KEY_SPOOF_DEVICE] as String))
7689
}
7790
}
7891
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.parallelc.micts.config
2+
import com.parallelc.micts.R
3+
import java.util.Locale
4+
5+
enum class Language(val id: Int, val toLocale: () -> Locale) {
6+
FollowSystem(R.string.follow_system, { Locale.getDefault() }),
7+
English(R.string.english, { Locale.ENGLISH }),
8+
Chinese(R.string.chinese, { Locale.CHINESE })
9+
}
10+
11+
object AppConfig {
12+
const val CONFIG_NAME = "app_config"
13+
const val KEY_LANGUAGE = "language"
14+
const val KEY_DEFAULT_DELAY = "default_delay"
15+
const val KEY_TILE_DELAY = "tile_delay"
16+
17+
val DEFAULT_CONFIG = mapOf<String, Any>(
18+
KEY_LANGUAGE to Language.FollowSystem.ordinal,
19+
KEY_DEFAULT_DELAY to 0L,
20+
KEY_TILE_DELAY to 400L,
21+
)
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.parallelc.micts.config
2+
3+
import android.annotation.SuppressLint
4+
import android.content.res.Resources
5+
import android.os.Build
6+
7+
enum class TriggerService(val isSupported: Boolean) {
8+
VIS(true),
9+
@SuppressLint("DiscouragedApi")
10+
CSHelper(Resources.getSystem().getIdentifier("config_defaultContextualSearchKey", "string", "android") != 0),
11+
ContextualSearchService(false);
12+
13+
companion object {
14+
fun getSupportedServices(): List<TriggerService> {
15+
return TriggerService.entries.filter { it.isSupported }
16+
}
17+
}
18+
}
19+
20+
object XposedConfig {
21+
const val CONFIG_NAME = "xposed_config"
22+
const val KEY_TRIGGER_SERVICE = "trigger_service"
23+
const val KEY_GESTURE_TRIGGER = "gesture_trigger"
24+
const val KEY_HOME_TRIGGER = "home_trigger"
25+
const val KEY_DEVICE_SPOOF = "device_spoof"
26+
const val KEY_SPOOF_MANUFACTURER = "spoof_manufacturer"
27+
const val KEY_SPOOF_BRAND = "spoof_brand"
28+
const val KEY_SPOOF_MODEL = "spoof_model"
29+
const val KEY_SPOOF_DEVICE = "spoof_device"
30+
31+
val DEFAULT_CONFIG = mapOf<String, Any>(
32+
KEY_TRIGGER_SERVICE to TriggerService.getSupportedServices().last().ordinal,
33+
KEY_GESTURE_TRIGGER to (Build.MANUFACTURER == "Xiaomi"),
34+
KEY_HOME_TRIGGER to (Build.MANUFACTURER == "Xiaomi"),
35+
KEY_DEVICE_SPOOF to true,
36+
KEY_SPOOF_MANUFACTURER to "Google",
37+
KEY_SPOOF_BRAND to "google",
38+
KEY_SPOOF_MODEL to "Pixel 8 Pro",
39+
KEY_SPOOF_DEVICE to "husky",
40+
)
41+
}

app/src/main/java/com/parallelc/micts/hooker/InvokeOmniHooker.kt

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.parallelc.micts.hooker
22

3-
import com.parallelc.micts.triggerCircleToSearch
3+
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
4+
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
5+
import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
6+
import com.parallelc.micts.module
7+
import com.parallelc.micts.ui.activity.triggerCircleToSearch
48
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
59
import io.github.libxposed.api.XposedInterface.Hooker
610
import io.github.libxposed.api.annotations.BeforeInvocation
@@ -12,7 +16,9 @@ class InvokeOmniHooker : Hooker {
1216
@JvmStatic
1317
@BeforeInvocation
1418
fun before(callback: BeforeHookCallback) {
15-
callback.returnAndSkip(triggerCircleToSearch(callback.args[2] as Int))
19+
if (module!!.getRemotePreferences(CONFIG_NAME).getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
20+
callback.returnAndSkip(triggerCircleToSearch(callback.args[2] as Int))
21+
}
1622
}
1723
}
1824
}

app/src/main/java/com/parallelc/micts/hooker/LongPressHomeHooker.kt

+9-31
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,37 @@
11
package com.parallelc.micts.hooker
22

33
import android.os.Bundle
4+
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
5+
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
6+
import com.parallelc.micts.config.XposedConfig.KEY_HOME_TRIGGER
47
import com.parallelc.micts.module
5-
import com.parallelc.micts.triggerCircleToSearch
6-
import io.github.libxposed.api.XposedInterface.AfterHookCallback
8+
import com.parallelc.micts.ui.activity.triggerCircleToSearch
79
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
810
import io.github.libxposed.api.XposedInterface.Hooker
9-
import io.github.libxposed.api.XposedInterface.MethodUnhooker
1011
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
11-
import io.github.libxposed.api.annotations.AfterInvocation
1212
import io.github.libxposed.api.annotations.BeforeInvocation
1313
import io.github.libxposed.api.annotations.XposedHooker
14-
import java.lang.reflect.Method
1514

1615
class LongPressHomeHooker {
1716
companion object {
18-
private var hookFunction = mutableMapOf<String, Method?>()
19-
2017
fun hook(param: SystemServerLoadedParam) {
2118
val shortCutActionsUtils = param.classLoader.loadClass("com.miui.server.input.util.ShortCutActionsUtils")
22-
hookFunction["launch_voice_assistant"] = shortCutActionsUtils.declaredMethods.firstOrNull { method: Method -> method.name == "launchVoiceAssistant" }
23-
hookFunction["launch_google_search"] = shortCutActionsUtils.declaredMethods.firstOrNull { method: Method -> method.name == "launchGoogleSearch" }
24-
module.hook(
19+
module!!.hook(
2520
shortCutActionsUtils.getDeclaredMethod("triggerFunction", String::class.java, String::class.java, Bundle::class.java, Boolean::class.java, String::class.java),
2621
TriggerFunctionHooker::class.java
2722
)
2823
}
2924

30-
@XposedHooker
31-
class LaunchFunctionHooker : Hooker {
32-
companion object {
33-
@JvmStatic
34-
@BeforeInvocation
35-
fun before(callback: BeforeHookCallback) {
36-
callback.returnAndSkip(triggerCircleToSearch(1))
37-
}
38-
}
39-
}
40-
4125
@XposedHooker
4226
class TriggerFunctionHooker : Hooker {
4327
companion object {
4428
@JvmStatic
4529
@BeforeInvocation
46-
fun before(callback: BeforeHookCallback) : MethodUnhooker<Method>? {
47-
if (callback.args[1] == "long_press_home_key" || callback.args[1] == "long_press_home_key_no_ui") {
48-
hookFunction[callback.args[0]]?.let { return module.hook(it, LaunchFunctionHooker::class.java) }
30+
fun before(callback: BeforeHookCallback) {
31+
if ((callback.args[1] == "long_press_home_key" || callback.args[1] == "long_press_home_key_no_ui") &&
32+
module!!.getRemotePreferences(CONFIG_NAME).getBoolean(KEY_HOME_TRIGGER, DEFAULT_CONFIG[KEY_HOME_TRIGGER] as Boolean)) {
33+
callback.returnAndSkip(triggerCircleToSearch(1))
4934
}
50-
return null
51-
}
52-
53-
@JvmStatic
54-
@AfterInvocation
55-
fun after(callback: AfterHookCallback, unhooker: MethodUnhooker<Method>?) {
56-
unhooker?.unhook()
5735
}
5836
}
5937
}

0 commit comments

Comments
 (0)