|
|
|
@@ -10,7 +10,6 @@ import android.content.pm.PackageManager
|
|
|
|
|
import android.hardware.camera2.CameraAccessException
|
|
|
|
|
import android.hardware.camera2.CameraCharacteristics
|
|
|
|
|
import android.hardware.camera2.CameraManager
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.os.Handler
|
|
|
|
|
import android.os.Looper
|
|
|
|
|
import android.os.Process
|
|
|
|
@@ -52,6 +51,8 @@ class NavButtons : XposedModule() {
|
|
|
|
|
private val systemUiHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
|
|
|
|
private val systemUiHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
|
|
|
|
private val honeyspaceLongPressTriggered = WeakHashMap<Any, Boolean>()
|
|
|
|
|
private val honeyspaceHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
|
|
|
|
private val honeyspaceHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
|
|
|
|
|
|
|
|
|
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
|
|
|
|
|
if (!HiddenApiBypass.addHiddenApiExemptions("")) {
|
|
|
|
@@ -374,111 +375,6 @@ class NavButtons : XposedModule() {
|
|
|
|
|
return installed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun hookSystemUiHomeLongClickIfPresent(navigationBarClass: Class<*>?) {
|
|
|
|
|
if (navigationBarClass == null) {
|
|
|
|
|
log("SystemUI navigation bar class not found: $SYSTEMUI_NAVIGATION_BAR")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val onHomeLongClickMethod = findMethodIfExists(
|
|
|
|
|
navigationBarClass,
|
|
|
|
|
"onHomeLongClick",
|
|
|
|
|
View::class.java,
|
|
|
|
|
) ?: run {
|
|
|
|
|
log("SystemUI onHomeLongClick() method not found.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deoptimizeMethodIfPresent(onHomeLongClickMethod)
|
|
|
|
|
hook(onHomeLongClickMethod).intercept { chain ->
|
|
|
|
|
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
|
|
|
|
|
if (longPressAction == NavButtonAction.STOCK) {
|
|
|
|
|
return@intercept chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
traceHome("NavigationBar.onHomeLongClick", "long=${longPressAction.storageValue}")
|
|
|
|
|
val homeView = chain.args[0] as? View
|
|
|
|
|
if (homeView != null) {
|
|
|
|
|
updateContextFromView(homeView)
|
|
|
|
|
systemUiLongPressTriggered[homeView] = true
|
|
|
|
|
markSystemUiLongClicked(homeView)
|
|
|
|
|
abortSystemUiNavigationGesture(chain.thisObject, homeView)
|
|
|
|
|
}
|
|
|
|
|
performHomeLongPressAction(longPressAction)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun hookSystemUiAssistStartIfPresent(assistManagerClass: Class<*>?) {
|
|
|
|
|
if (assistManagerClass == null) {
|
|
|
|
|
log("SystemUI assist manager class not found: $SYSTEMUI_ASSIST_MANAGER")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val startAssistMethod = findMethodIfExists(
|
|
|
|
|
assistManagerClass,
|
|
|
|
|
"startAssist",
|
|
|
|
|
Bundle::class.java,
|
|
|
|
|
) ?: run {
|
|
|
|
|
log("SystemUI startAssist(Bundle) method not found.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deoptimizeMethodIfPresent(startAssistMethod)
|
|
|
|
|
hook(startAssistMethod).intercept { chain ->
|
|
|
|
|
val args = chain.args[0] as? Bundle ?: return@intercept chain.proceed()
|
|
|
|
|
val invocationType = args.getInt(ASSIST_INVOCATION_TYPE_KEY, ASSIST_INVOCATION_TYPE_UNKNOWN)
|
|
|
|
|
traceHome("AssistManager.startAssist", "invocationType=$invocationType")
|
|
|
|
|
if (invocationType != ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS) {
|
|
|
|
|
return@intercept chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
|
|
|
|
|
if (longPressAction == NavButtonAction.STOCK) {
|
|
|
|
|
return@intercept chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
traceHome("AssistManager.startAssist", "blocked long=${longPressAction.storageValue}")
|
|
|
|
|
performHomeLongPressAction(longPressAction)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun hookSystemUiOverviewProxyStartAssistantIfPresent(overviewProxyServiceClass: Class<*>?) {
|
|
|
|
|
if (overviewProxyServiceClass == null) {
|
|
|
|
|
log("SystemUI overview proxy service class not found: $SYSTEMUI_OVERVIEW_PROXY_SERVICE")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val startAssistantMethod = findMethodIfExists(
|
|
|
|
|
overviewProxyServiceClass,
|
|
|
|
|
"startAssistant",
|
|
|
|
|
Bundle::class.java,
|
|
|
|
|
) ?: run {
|
|
|
|
|
log("SystemUI OverviewProxyService.startAssistant(Bundle) method not found.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deoptimizeMethodIfPresent(startAssistantMethod)
|
|
|
|
|
hook(startAssistantMethod).intercept { chain ->
|
|
|
|
|
val args = chain.args[0] as? Bundle ?: return@intercept chain.proceed()
|
|
|
|
|
val invocationType = args.getInt(ASSIST_INVOCATION_TYPE_KEY, ASSIST_INVOCATION_TYPE_UNKNOWN)
|
|
|
|
|
traceHome("OverviewProxyService.startAssistant", "invocationType=$invocationType")
|
|
|
|
|
if (invocationType != ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS) {
|
|
|
|
|
return@intercept chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
|
|
|
|
|
if (longPressAction == NavButtonAction.STOCK) {
|
|
|
|
|
return@intercept chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
traceHome("OverviewProxyService.startAssistant", "blocked long=${longPressAction.storageValue}")
|
|
|
|
|
performHomeLongPressAction(longPressAction)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun hookHoneyspaceTaskbar(classLoader: ClassLoader?): Boolean {
|
|
|
|
|
val navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader) ?: run {
|
|
|
|
|
log("Launcher class not found: $HONEYSPACE_NAV_BUTTON_VIEW")
|
|
|
|
@@ -516,6 +412,8 @@ class NavButtons : XposedModule() {
|
|
|
|
|
when (motionEvent.actionMasked) {
|
|
|
|
|
MotionEvent.ACTION_DOWN -> {
|
|
|
|
|
honeyspaceLongPressTriggered[navButtonView] = false
|
|
|
|
|
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
|
|
|
|
|
honeyspaceHomeStockPressCanceled.remove(navButtonView)
|
|
|
|
|
if (buttonId == NavButtonId.HOME) {
|
|
|
|
|
val config = getButtonConfig(buttonId)
|
|
|
|
|
traceHome(
|
|
|
|
@@ -525,6 +423,7 @@ class NavButtons : XposedModule() {
|
|
|
|
|
}
|
|
|
|
|
val result = chain.proceed()
|
|
|
|
|
rescheduleHoneyspaceLongPress(navButtonView, runnableClass, buttonId)
|
|
|
|
|
scheduleHoneyspaceHomeStockLongPressGuard(navButtonView, buttonId)
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -537,14 +436,18 @@ class NavButtons : XposedModule() {
|
|
|
|
|
return@intercept proceedWithCanceledAction(motionEvent) {
|
|
|
|
|
chain.proceed()
|
|
|
|
|
}.also {
|
|
|
|
|
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
|
|
|
|
|
honeyspaceLongPressTriggered.remove(navButtonView)
|
|
|
|
|
honeyspaceHomeStockPressCanceled.remove(navButtonView)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val pressAction = getButtonConfig(buttonId).pressAction
|
|
|
|
|
val stockHomePressCanceled =
|
|
|
|
|
buttonId == NavButtonId.HOME && honeyspaceHomeStockPressCanceled[navButtonView] == true
|
|
|
|
|
val shouldOverridePress =
|
|
|
|
|
pressAction != NavButtonAction.STOCK &&
|
|
|
|
|
(navButtonView as? View)?.isPressed == true &&
|
|
|
|
|
((navButtonView as? View)?.isPressed == true || stockHomePressCanceled) &&
|
|
|
|
|
!longPressTriggered
|
|
|
|
|
|
|
|
|
|
if (shouldOverridePress) {
|
|
|
|
@@ -552,13 +455,21 @@ class NavButtons : XposedModule() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val result = chain.proceed()
|
|
|
|
|
if (stockHomePressCanceled && pressAction == NavButtonAction.STOCK) {
|
|
|
|
|
dispatchHoneyspaceHomeTap(navButtonView)
|
|
|
|
|
}
|
|
|
|
|
if (shouldOverridePress) {
|
|
|
|
|
updateContextFromView(navButtonView)
|
|
|
|
|
performConfiguredAction(pressAction)
|
|
|
|
|
}
|
|
|
|
|
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
|
|
|
|
|
honeyspaceLongPressTriggered.remove(navButtonView)
|
|
|
|
|
honeyspaceHomeStockPressCanceled.remove(navButtonView)
|
|
|
|
|
if (buttonId == NavButtonId.HOME) {
|
|
|
|
|
traceHome("Honeyspace.onTouchEvent UP", "override=$shouldOverridePress")
|
|
|
|
|
traceHome(
|
|
|
|
|
"Honeyspace.onTouchEvent UP",
|
|
|
|
|
"override=$shouldOverridePress ${honeyspaceHomeState(navButtonView)}",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
@@ -567,7 +478,9 @@ class NavButtons : XposedModule() {
|
|
|
|
|
if (buttonId == NavButtonId.HOME) {
|
|
|
|
|
traceHome("Honeyspace.onTouchEvent CANCEL", "clearing long-press state")
|
|
|
|
|
}
|
|
|
|
|
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
|
|
|
|
|
honeyspaceLongPressTriggered.remove(navButtonView)
|
|
|
|
|
honeyspaceHomeStockPressCanceled.remove(navButtonView)
|
|
|
|
|
chain.proceed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -601,6 +514,7 @@ class NavButtons : XposedModule() {
|
|
|
|
|
}
|
|
|
|
|
updateContextFromView(navButtonView)
|
|
|
|
|
markHoneyspaceLongClicked(navButtonView)
|
|
|
|
|
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
|
|
|
|
|
performConfiguredAction(longPressAction)
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
@@ -1037,6 +951,35 @@ class NavButtons : XposedModule() {
|
|
|
|
|
(keyButtonView as? View)?.removeCallbacks(guard)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun scheduleHoneyspaceHomeStockLongPressGuard(navButtonView: Any, buttonId: NavButtonId) {
|
|
|
|
|
if (buttonId != NavButtonId.HOME) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (getButtonConfig(buttonId).longPressAction == NavButtonAction.STOCK) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val view = navButtonView as? View ?: return
|
|
|
|
|
val delayMs = (ViewConfiguration.getLongPressTimeout() - STOCK_LONG_PRESS_GUARD_EARLY_MS)
|
|
|
|
|
.coerceAtLeast(STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS)
|
|
|
|
|
val guard = Runnable {
|
|
|
|
|
if (!view.isPressed || honeyspaceLongPressTriggered[navButtonView] == true) {
|
|
|
|
|
return@Runnable
|
|
|
|
|
}
|
|
|
|
|
traceHome("Honeyspace stock long-press guard", "canceling original Home key after ${delayMs}ms")
|
|
|
|
|
honeyspaceHomeStockPressCanceled[navButtonView] = true
|
|
|
|
|
sendHoneyspaceCanceledKeyUp(navButtonView)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
honeyspaceHomeStockLongPressGuards[navButtonView] = guard
|
|
|
|
|
view.postDelayed(guard, delayMs.toLong())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun cancelHoneyspaceHomeStockLongPressGuard(navButtonView: Any) {
|
|
|
|
|
val guard = honeyspaceHomeStockLongPressGuards.remove(navButtonView) ?: return
|
|
|
|
|
(navButtonView as? View)?.removeCallbacks(guard)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun abortSystemUiKeyGesture(keyButtonView: Any) {
|
|
|
|
|
try {
|
|
|
|
|
callMethod(keyButtonView, "abortCurrentGesture")
|
|
|
|
@@ -1059,6 +1002,45 @@ class NavButtons : XposedModule() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun sendHoneyspaceCanceledKeyUp(navButtonView: Any) {
|
|
|
|
|
if (callIntIntMethodIfExists(
|
|
|
|
|
navButtonView,
|
|
|
|
|
listOf("sendEvent", "sendKeyEvent"),
|
|
|
|
|
KeyEvent.ACTION_UP,
|
|
|
|
|
KeyEvent.FLAG_CANCELED or KeyEvent.FLAG_CANCELED_LONG_PRESS,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
val inputManager = callStaticMethod(
|
|
|
|
|
findClass(INPUT_MANAGER_CLASS, null),
|
|
|
|
|
"getInstance",
|
|
|
|
|
) ?: run {
|
|
|
|
|
log("InputManager unavailable for Honeyspace Home cancel.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
val now = SystemClock.uptimeMillis()
|
|
|
|
|
injectKeyEvent(
|
|
|
|
|
inputManager,
|
|
|
|
|
KeyEvent(
|
|
|
|
|
now,
|
|
|
|
|
now,
|
|
|
|
|
KeyEvent.ACTION_UP,
|
|
|
|
|
KeyEvent.KEYCODE_HOME,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
KeyEvent.KEYCODE_UNKNOWN,
|
|
|
|
|
0,
|
|
|
|
|
KeyEvent.FLAG_CANCELED or KeyEvent.FLAG_CANCELED_LONG_PRESS,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
} catch (error: Throwable) {
|
|
|
|
|
log("Honeyspace Home cancel failed: ${error.javaClass.simpleName}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun dispatchSystemUiHomeTap(keyButtonView: Any) {
|
|
|
|
|
traceHome("dispatchSystemUiHomeTap", "sending stock tap after guard cancel")
|
|
|
|
|
try {
|
|
|
|
@@ -1081,19 +1063,14 @@ class NavButtons : XposedModule() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun abortSystemUiNavigationGesture(navigationBar: Any?, homeView: View) {
|
|
|
|
|
try {
|
|
|
|
|
callMethod(homeView, "abortCurrentGesture")
|
|
|
|
|
} catch (_: Throwable) {
|
|
|
|
|
// Ignore variants without this helper on the home button view.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val navigationBarView = getFieldValue(navigationBar, "mView")
|
|
|
|
|
try {
|
|
|
|
|
callMethod(navigationBarView ?: return, "abortCurrentGesture")
|
|
|
|
|
} catch (_: Throwable) {
|
|
|
|
|
// Ignore variants without this helper on the navigation bar view.
|
|
|
|
|
private fun dispatchHoneyspaceHomeTap(navButtonView: Any) {
|
|
|
|
|
traceHome("dispatchHoneyspaceHomeTap", "sending stock tap after guard cancel")
|
|
|
|
|
if (callIntIntMethodIfExists(navButtonView, listOf("sendEvent", "sendKeyEvent"), KeyEvent.ACTION_DOWN, 0) &&
|
|
|
|
|
callIntIntMethodIfExists(navButtonView, listOf("sendEvent", "sendKeyEvent"), KeyEvent.ACTION_UP, 0)
|
|
|
|
|
) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
sendKeyPress(KeyEvent.KEYCODE_HOME)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun rescheduleSystemUiLongPress(keyButtonView: Any, buttonId: NavButtonId) {
|
|
|
|
@@ -1199,6 +1176,12 @@ class NavButtons : XposedModule() {
|
|
|
|
|
"stockCanceled=${systemUiHomeStockPressCanceled[keyButtonView] == true}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun honeyspaceHomeState(navButtonView: Any): String {
|
|
|
|
|
return "pressed=${(navButtonView as? View)?.isPressed == true} " +
|
|
|
|
|
"triggered=${honeyspaceLongPressTriggered[navButtonView] == true} " +
|
|
|
|
|
"stockCanceled=${honeyspaceHomeStockPressCanceled[navButtonView] == true}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun sendEventArgsToTraceString(args: Any?): String {
|
|
|
|
|
val values = argsToList(args)
|
|
|
|
|
return values.mapIndexed { index, arg ->
|
|
|
|
@@ -1306,10 +1289,35 @@ class NavButtons : XposedModule() {
|
|
|
|
|
return findMethod(target.javaClass, methodName, *parameterTypes).invoke(target, *args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun callIntIntMethodIfExists(
|
|
|
|
|
target: Any,
|
|
|
|
|
methodNames: List<String>,
|
|
|
|
|
first: Int,
|
|
|
|
|
second: Int,
|
|
|
|
|
): Boolean {
|
|
|
|
|
methodNames.forEach { methodName ->
|
|
|
|
|
findMethodsNamed(target.javaClass, methodName)
|
|
|
|
|
.firstOrNull { method ->
|
|
|
|
|
val types = method.parameterTypes
|
|
|
|
|
types.size >= 2 &&
|
|
|
|
|
types[0] == Int::class.javaPrimitiveType &&
|
|
|
|
|
types[1] == Int::class.javaPrimitiveType
|
|
|
|
|
}
|
|
|
|
|
?.let { method ->
|
|
|
|
|
return try {
|
|
|
|
|
method.invoke(target, first, second)
|
|
|
|
|
true
|
|
|
|
|
} catch (_: Throwable) {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private companion object {
|
|
|
|
|
const val DEBUG_TRACE = true
|
|
|
|
|
const val DEBUG_TRACE = false
|
|
|
|
|
const val TAG = "NavButtons"
|
|
|
|
|
const val ASSIST_INVOCATION_TYPE_KEY = "invocation_type"
|
|
|
|
|
const val ASSIST_INVOCATION_TYPE_UNKNOWN = 0
|
|
|
|
|
const val ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 5
|
|
|
|
|
const val INPUT_MANAGER_CLASS = "android.hardware.input.InputManager"
|
|
|
|
@@ -1319,13 +1327,8 @@ class NavButtons : XposedModule() {
|
|
|
|
|
const val STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS = 100
|
|
|
|
|
const val SYSTEMUI_PACKAGE = "com.android.systemui"
|
|
|
|
|
const val SYSTEM_SERVER_PHONE_WINDOW_MANAGER = "com.android.server.policy.PhoneWindowManager"
|
|
|
|
|
const val SYSTEMUI_ASSIST_MANAGER = "com.android.systemui.assist.AssistManager"
|
|
|
|
|
const val SYSTEMUI_KEY_BUTTON_VIEW =
|
|
|
|
|
"com.android.systemui.navigationbar.views.buttons.KeyButtonView"
|
|
|
|
|
const val SYSTEMUI_NAVIGATION_BAR =
|
|
|
|
|
"com.android.systemui.navigationbar.NavigationBar"
|
|
|
|
|
const val SYSTEMUI_OVERVIEW_PROXY_SERVICE =
|
|
|
|
|
"com.android.systemui.recents.OverviewProxyService"
|
|
|
|
|
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
|
|
|
|
|
const val HONEYSPACE_NAV_BUTTON_VIEW =
|
|
|
|
|
"com.honeyspace.ui.honeypots.taskbar.presentation.NavigationBarKeyButtonView"
|
|
|
|
|