Skip to content

Commit 0a0f063

Browse files
committed
feat: add vibrate
1 parent bbc31b6 commit 0a0f063

File tree

11 files changed

+109
-16
lines changed

11 files changed

+109
-16
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33

4+
<uses-permission android:name="android.permission.VIBRATE" />
45
<application
56
android:name=".MainApplication"
67
android:description="@string/xposed_description"

app/src/main/java/com/parallelc/micts/config/AppConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ object AppConfig {
1313
const val KEY_LANGUAGE = "language"
1414
const val KEY_DEFAULT_DELAY = "default_delay"
1515
const val KEY_TILE_DELAY = "tile_delay"
16+
const val KEY_VIBRATE = "vibrate"
1617

1718
val DEFAULT_CONFIG = mapOf<String, Any>(
1819
KEY_LANGUAGE to Language.FollowSystem.ordinal,
1920
KEY_DEFAULT_DELAY to 0L,
2021
KEY_TILE_DELAY to 400L,
22+
KEY_VIBRATE to false,
2123
)
2224
}

app/src/main/java/com/parallelc/micts/config/XposedConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ object XposedConfig {
3535
const val KEY_SPOOF_BRAND = "spoof_brand"
3636
const val KEY_SPOOF_MODEL = "spoof_model"
3737
const val KEY_SPOOF_DEVICE = "spoof_device"
38+
const val KEY_VIBRATE = "vibrate"
3839

3940
val DEFAULT_CONFIG = mapOf<String, Any>(
4041
KEY_TRIGGER_SERVICE to TriggerService.getSupportedServices().last().ordinal,
@@ -45,5 +46,6 @@ object XposedConfig {
4546
KEY_SPOOF_BRAND to "google",
4647
KEY_SPOOF_MODEL to "Pixel 8 Pro",
4748
KEY_SPOOF_DEVICE to "husky",
49+
KEY_VIBRATE to false,
4850
)
4951
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.parallelc.micts.hooker
22

3+
import android.content.Context
34
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
45
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
56
import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
7+
import com.parallelc.micts.config.XposedConfig.KEY_VIBRATE
68
import com.parallelc.micts.module
79
import com.parallelc.micts.ui.activity.triggerCircleToSearch
810
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
@@ -16,8 +18,15 @@ class InvokeOmniHooker : Hooker {
1618
@JvmStatic
1719
@BeforeInvocation
1820
fun before(callback: BeforeHookCallback) {
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+
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
22+
if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
23+
callback.returnAndSkip(
24+
triggerCircleToSearch(
25+
callback.args[2] as Int,
26+
callback.args[0] as Context,
27+
prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean)
28+
)
29+
)
2130
}
2231
}
2332
}

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
package com.parallelc.micts.hooker
22

3+
import android.content.Context
34
import android.os.Bundle
45
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
56
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
67
import com.parallelc.micts.config.XposedConfig.KEY_HOME_TRIGGER
8+
import com.parallelc.micts.config.XposedConfig.KEY_VIBRATE
79
import com.parallelc.micts.module
810
import com.parallelc.micts.ui.activity.triggerCircleToSearch
911
import io.github.libxposed.api.XposedInterface.BeforeHookCallback
1012
import io.github.libxposed.api.XposedInterface.Hooker
1113
import io.github.libxposed.api.XposedModuleInterface.SystemServerLoadedParam
1214
import io.github.libxposed.api.annotations.BeforeInvocation
1315
import io.github.libxposed.api.annotations.XposedHooker
16+
import java.lang.reflect.Field
1417

1518
class LongPressHomeHooker {
1619
companion object {
20+
private lateinit var mContext: Field
21+
1722
fun hook(param: SystemServerLoadedParam) {
1823
val shortCutActionsUtils = param.classLoader.loadClass("com.miui.server.input.util.ShortCutActionsUtils")
1924
module!!.hook(
2025
shortCutActionsUtils.getDeclaredMethod("triggerFunction", String::class.java, String::class.java, Bundle::class.java, Boolean::class.java, String::class.java),
2126
TriggerFunctionHooker::class.java
2227
)
28+
mContext = shortCutActionsUtils.getDeclaredField("mContext")
29+
mContext.isAccessible = true
2330
}
2431

2532
@XposedHooker
@@ -28,9 +35,18 @@ class LongPressHomeHooker {
2835
@JvmStatic
2936
@BeforeInvocation
3037
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))
38+
if (callback.args[1] == "long_press_home_key" || callback.args[1] == "long_press_home_key_no_ui") {
39+
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
40+
if (prefs.getBoolean(KEY_HOME_TRIGGER, DEFAULT_CONFIG[KEY_HOME_TRIGGER] as Boolean)) {
41+
val context = runCatching { mContext.get(callback.thisObject) as? Context }.getOrNull()
42+
callback.returnAndSkip(
43+
triggerCircleToSearch(
44+
1,
45+
context,
46+
prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean)
47+
)
48+
)
49+
}
3450
}
3551
}
3652
}

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package com.parallelc.micts.hooker
22

3+
import android.content.Context
34
import android.view.MotionEvent
45
import android.view.View
56
import android.view.ViewConfiguration
67
import com.parallelc.micts.config.XposedConfig.CONFIG_NAME
78
import com.parallelc.micts.config.XposedConfig.DEFAULT_CONFIG
89
import com.parallelc.micts.config.XposedConfig.KEY_GESTURE_TRIGGER
10+
import com.parallelc.micts.config.XposedConfig.KEY_VIBRATE
911
import com.parallelc.micts.module
1012
import com.parallelc.micts.ui.activity.triggerCircleToSearch
1113
import io.github.libxposed.api.XposedInterface.AfterHookCallback
1214
import io.github.libxposed.api.XposedInterface.Hooker
1315
import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
1416
import io.github.libxposed.api.annotations.AfterInvocation
1517
import io.github.libxposed.api.annotations.XposedHooker
18+
import java.lang.ref.WeakReference
1619
import java.lang.reflect.Field
1720
import kotlin.math.abs
1821

@@ -23,6 +26,7 @@ class NavStubViewHooker {
2326
private lateinit var mInitX: Field
2427
private lateinit var mCurrY: Field
2528
private lateinit var mInitY: Field
29+
private var mContext: WeakReference<Context>? = null
2630

2731
fun hook(param: PackageLoadedParam) {
2832
val navStubView = param.classLoader.loadClass("com.miui.home.recents.NavStubView")
@@ -40,15 +44,21 @@ class NavStubViewHooker {
4044
mInitY = navStubView.getDeclaredField("mInitY")
4145
mInitY.isAccessible = true
4246
module!!.hook(navStubView.getDeclaredMethod("onTouchEvent", MotionEvent::class.java), OnTouchEventHooker::class.java)
47+
module!!.hook(navStubView.getDeclaredConstructor(Context::class.java), ConstructorHooker::class.java)
4348
}
4449
}
4550

4651
@XposedHooker
4752
class OnTouchEventHooker : Hooker {
4853
companion object {
4954
private val mCheckLongPress = Runnable {
50-
if (module!!.getRemotePreferences(CONFIG_NAME).getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
51-
triggerCircleToSearch(1)
55+
val prefs = module!!.getRemotePreferences(CONFIG_NAME)
56+
if (prefs.getBoolean(KEY_GESTURE_TRIGGER, DEFAULT_CONFIG[KEY_GESTURE_TRIGGER] as Boolean)) {
57+
triggerCircleToSearch(
58+
1,
59+
mContext?.get(),
60+
prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean)
61+
)
5262
}
5363
}
5464

@@ -73,5 +83,16 @@ class NavStubViewHooker {
7383
}
7484
}
7585
}
86+
87+
@XposedHooker
88+
class ConstructorHooker : Hooker {
89+
companion object {
90+
@JvmStatic
91+
@AfterInvocation
92+
fun after(callback: AfterHookCallback) {
93+
mContext = WeakReference(callback.args[0] as Context)
94+
}
95+
}
96+
}
7697
}
7798
}

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,28 @@ package com.parallelc.micts.ui.activity
22

33
import android.annotation.SuppressLint
44
import android.app.Activity
5+
import android.content.Context
6+
import android.media.AudioAttributes
57
import android.os.Build
68
import android.os.Bundle
79
import android.os.IBinder
810
import android.os.SystemClock
11+
import android.os.VibrationEffect
12+
import android.os.Vibrator
913
import android.util.Log
1014
import android.widget.Toast
15+
import com.parallelc.micts.R
1116
import com.parallelc.micts.config.AppConfig.CONFIG_NAME
1217
import com.parallelc.micts.config.AppConfig.DEFAULT_CONFIG
1318
import com.parallelc.micts.config.AppConfig.KEY_DEFAULT_DELAY
1419
import com.parallelc.micts.config.AppConfig.KEY_TILE_DELAY
20+
import com.parallelc.micts.config.AppConfig.KEY_VIBRATE
1521
import com.parallelc.micts.module
16-
import com.parallelc.micts.R
1722
import org.lsposed.hiddenapibypass.HiddenApiBypass
1823

1924
@SuppressLint("PrivateApi")
20-
fun triggerCircleToSearch(entryPoint: Int): Boolean {
21-
return runCatching {
25+
fun triggerCircleToSearch(entryPoint: Int, context: Context?, vibrate: Boolean): Boolean {
26+
val result = runCatching {
2227
val bundle = Bundle()
2328
bundle.putLong("invocation_time_ms", SystemClock.elapsedRealtime())
2429
bundle.putInt("omni.entry_point", entryPoint)
@@ -32,20 +37,40 @@ fun triggerCircleToSearch(entryPoint: Int): Boolean {
3237
HiddenApiBypass.invoke(iVimsClass, vims, "showSessionFromSession", null, bundle, 7) as Boolean
3338
}
3439
}.onFailure { e ->
35-
val errMsg = "triggerCircleToSearch failed: " + e.stackTraceToString()
40+
val errMsg = "triggerCircleToSearch invoke omni failed: " + e.stackTraceToString()
3641
module?.log(errMsg) ?: Log.e("MiCTS", errMsg)
3742
}.getOrDefault(false)
43+
if (result && vibrate && context != null) {
44+
runCatching {
45+
(context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).run {
46+
val attr = AudioAttributes.Builder()
47+
.setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
48+
.setFlags(128)
49+
.build()
50+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
51+
vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK), attr)
52+
} else {
53+
vibrate(longArrayOf(0, 1, 75, 76), -1, attr)
54+
}
55+
}
56+
}.onFailure { e ->
57+
val errMsg = "triggerCircleToSearch vibrate failed: " + e.stackTraceToString()
58+
module?.log(errMsg) ?: Log.e("MiCTS", errMsg)
59+
}
60+
}
61+
return result
3862
}
3963

4064
class MainActivity : Activity() {
4165
override fun onCreate(savedInstanceState: Bundle?) {
4266
super.onCreate(savedInstanceState)
67+
val prefs = getSharedPreferences(CONFIG_NAME, MODE_PRIVATE)
4368
val key = if (intent.getBooleanExtra("from_tile", false)) KEY_TILE_DELAY else KEY_DEFAULT_DELAY
44-
val delay = getSharedPreferences(CONFIG_NAME, MODE_PRIVATE).getLong(key, DEFAULT_CONFIG[key] as Long)
69+
val delay = prefs.getLong(key, DEFAULT_CONFIG[key] as Long)
4570
if (delay > 0) {
4671
Thread.sleep(delay)
4772
}
48-
if (!triggerCircleToSearch(1)) {
73+
if (!triggerCircleToSearch(1, this, prefs.getBoolean(KEY_VIBRATE, DEFAULT_CONFIG[KEY_VIBRATE] as Boolean))) {
4974
Toast.makeText(this, getString(R.string.trigger_failed), Toast.LENGTH_SHORT).show()
5075
}
5176
finish()

app/src/main/java/com/parallelc/micts/ui/activity/SettingsActivity.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ fun SettingsPage(
237237
valueRange = 0f..2000f
238238
)
239239

240+
ListItem(
241+
headlineContent = { Text(stringResource(R.string.vibrate)) },
242+
trailingContent = {
243+
Switch(
244+
checked = appConfig[AppConfig.KEY_VIBRATE] as Boolean,
245+
onCheckedChange = {
246+
viewModel.updateAppConfig(AppConfig.KEY_VIBRATE, it)
247+
xposedService?.run { viewModel.updateXposedConfig(XposedConfig.KEY_VIBRATE, it) }
248+
}
249+
)
250+
}
251+
)
252+
240253
ListItem(
241254
headlineContent = {
242255
Text(

app/src/main/java/com/parallelc/micts/ui/viewmodel/SettingsViewModel.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
5757
var triggerServiceExpanded = mutableStateOf(false)
5858

5959
init {
60+
appConfigPref = application.getSharedPreferences(AppConfig.CONFIG_NAME, MODE_PRIVATE)
61+
_appConfig.value = AppConfig.DEFAULT_CONFIG + appConfigPref.all.filterValues { it != null }.mapValues { it.value as Any }
62+
_locale.value = Language.entries[_appConfig.value[AppConfig.KEY_LANGUAGE] as Int].toLocale()
6063
XposedServiceHelper.registerListener(object : XposedServiceHelper.OnServiceListener {
6164
override fun onServiceBind(service: XposedService) {
6265
viewModelScope.launch(Dispatchers.IO) {
@@ -68,6 +71,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
6871
_xposedConfig.value.forEach {
6972
checkScope(scope, it.key, it.value)
7073
}
74+
updateXposedConfig(XposedConfig.KEY_VIBRATE, _appConfig.value[AppConfig.KEY_VIBRATE]!!)
7175
}.onFailure { e ->
7276
if (e is ServiceException) {
7377
_xposedService.value = null
@@ -82,9 +86,6 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
8286
}
8387
}
8488
})
85-
appConfigPref = application.getSharedPreferences(AppConfig.CONFIG_NAME, MODE_PRIVATE)
86-
_appConfig.value = AppConfig.DEFAULT_CONFIG + appConfigPref.all.filterValues { it != null }.mapValues { it.value as Any }
87-
_locale.value = Language.entries[_appConfig.value[AppConfig.KEY_LANGUAGE] as Int].toLocale()
8889
}
8990

9091
fun updateAppConfig(key: String, value: Any) {
@@ -99,6 +100,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
99100
when (value) {
100101
is Long -> appConfigPref.edit().putLong(key, value).apply()
101102
is Int -> appConfigPref.edit().putInt(key, value).apply()
103+
is Boolean -> appConfigPref.edit().putBoolean(key, value).apply()
102104
}
103105
}
104106
}

app/src/main/res/values-zh/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<string name="app_settings">应用设置</string>
88
<string name="default_trigger_delay">默认触发延迟</string>
99
<string name="tile_trigger_delay">磁贴触发延迟</string>
10+
<string name="vibrate">震动</string>
1011
<string name="module_settings">模块设置</string>
1112
<string name="access_xposed_service_failed">访问xposed服务失败</string>
1213
<string name="system_trigger_service">系统触发服务</string>

0 commit comments

Comments
 (0)