Add initial AOSP button support
This commit is contained in:
@@ -3,6 +3,11 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="org.lsposed.manager" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.view.ViewConfiguration
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import io.github.libxposed.service.XposedService
|
import io.github.libxposed.service.XposedService
|
||||||
import io.github.libxposed.service.XposedServiceHelper
|
import io.github.libxposed.service.XposedServiceHelper
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
enum class NavButtonId(
|
enum class NavButtonId(
|
||||||
val preferencePrefix: String,
|
val preferencePrefix: String,
|
||||||
@@ -68,6 +69,7 @@ class NavButtonSettingsStore(context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
ensurePrivilegedActionToken()
|
||||||
registerRemotePreferencesListener(localPreferences)
|
registerRemotePreferencesListener(localPreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +199,15 @@ class NavButtonSettingsStore(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ensurePrivilegedActionToken() {
|
||||||
|
if (localPreferences.contains(KEY_PRIVILEGED_ACTION_TOKEN)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeToPreferences(localPreferences, System.currentTimeMillis()) {
|
||||||
|
putString(KEY_PRIVILEGED_ACTION_TOKEN, UUID.randomUUID().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun writeToPreferences(
|
private fun writeToPreferences(
|
||||||
preferences: SharedPreferences,
|
preferences: SharedPreferences,
|
||||||
updatedAtMs: Long,
|
updatedAtMs: Long,
|
||||||
@@ -220,6 +231,7 @@ class NavButtonSettingsStore(context: Context) {
|
|||||||
const val KEY_SIDE_ARROW_LONG_PRESS_ACTION = "side_arrow_long_press_action"
|
const val KEY_SIDE_ARROW_LONG_PRESS_ACTION = "side_arrow_long_press_action"
|
||||||
const val KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = "side_arrow_repeat_steps_per_second"
|
const val KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND = "side_arrow_repeat_steps_per_second"
|
||||||
const val KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS = "side_arrow_long_press_duration_ms"
|
const val KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS = "side_arrow_long_press_duration_ms"
|
||||||
|
const val KEY_PRIVILEGED_ACTION_TOKEN = "privileged_action_token"
|
||||||
private const val KEY_LAST_UPDATED_AT_MS = "last_updated_at_ms"
|
private const val KEY_LAST_UPDATED_AT_MS = "last_updated_at_ms"
|
||||||
|
|
||||||
const val MIN_DURATION_MS = 100
|
const val MIN_DURATION_MS = 100
|
||||||
@@ -358,6 +370,12 @@ class NavButtonSettingsStore(context: Context) {
|
|||||||
editor.remove(KEY_SIDE_ARROWS_ENABLED)
|
editor.remove(KEY_SIDE_ARROWS_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source.contains(KEY_PRIVILEGED_ACTION_TOKEN)) {
|
||||||
|
editor.putString(KEY_PRIVILEGED_ACTION_TOKEN, source.getString(KEY_PRIVILEGED_ACTION_TOKEN, null))
|
||||||
|
} else {
|
||||||
|
editor.remove(KEY_PRIVILEGED_ACTION_TOKEN)
|
||||||
|
}
|
||||||
|
|
||||||
if (source.contains(KEY_LAST_UPDATED_AT_MS)) {
|
if (source.contains(KEY_LAST_UPDATED_AT_MS)) {
|
||||||
editor.putLong(KEY_LAST_UPDATED_AT_MS, source.getLong(KEY_LAST_UPDATED_AT_MS, 0L))
|
editor.putLong(KEY_LAST_UPDATED_AT_MS, source.getLong(KEY_LAST_UPDATED_AT_MS, 0L))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.hardware.camera2.CameraAccessException
|
import android.hardware.camera2.CameraAccessException
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.os.Process
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.InputEvent
|
import android.view.InputEvent
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
@@ -59,12 +62,18 @@ class NavButtons : XposedModule() {
|
|||||||
private var systemUiHookInstalled = false
|
private var systemUiHookInstalled = false
|
||||||
private var systemServerHookInstalled = false
|
private var systemServerHookInstalled = false
|
||||||
private var launcherHookInstalled = false
|
private var launcherHookInstalled = false
|
||||||
|
private var aospTaskbarHookInstalled = false
|
||||||
private var lastHomeLongPressHandledAtMs = 0L
|
private var lastHomeLongPressHandledAtMs = 0L
|
||||||
private var flashlightCameraId: String? = null
|
private var flashlightCameraId: String? = null
|
||||||
private var flashlightEnabled = false
|
private var flashlightEnabled = false
|
||||||
private val systemUiLongPressTriggered = WeakHashMap<Any, Boolean>()
|
private val systemUiLongPressTriggered = WeakHashMap<Any, Boolean>()
|
||||||
private val systemUiHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
private val systemUiHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
||||||
private val systemUiHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
private val systemUiHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
||||||
|
private val aospTaskbarButtonIds = WeakHashMap<Any, NavButtonId>()
|
||||||
|
private val aospTaskbarControllers = WeakHashMap<Any, Any>()
|
||||||
|
private val aospTaskbarLongPressRunnables = WeakHashMap<Any, Runnable>()
|
||||||
|
private val aospTaskbarLongPressTriggered = WeakHashMap<Any, Boolean>()
|
||||||
|
private val aospTaskbarBackLongPressTriggered = WeakHashMap<Any, Boolean>()
|
||||||
private val honeyspaceLongPressTriggered = WeakHashMap<Any, Boolean>()
|
private val honeyspaceLongPressTriggered = WeakHashMap<Any, Boolean>()
|
||||||
private val honeyspaceHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
private val honeyspaceHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
|
||||||
private val honeyspaceHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
private val honeyspaceHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
|
||||||
@@ -78,6 +87,8 @@ class NavButtons : XposedModule() {
|
|||||||
private var foldedSideArrowProviderHookInstalled = false
|
private var foldedSideArrowProviderHookInstalled = false
|
||||||
private var samsungNavigationBarViewHookInstalled = false
|
private var samsungNavigationBarViewHookInstalled = false
|
||||||
private var settingsChangedReceiverRegistered = false
|
private var settingsChangedReceiverRegistered = false
|
||||||
|
private var privilegedActionReceiverRegistered = false
|
||||||
|
private var privilegedActionToken: String? = null
|
||||||
private var sideArrowsEnabledOverride: Boolean? = null
|
private var sideArrowsEnabledOverride: Boolean? = null
|
||||||
|
|
||||||
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
|
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
|
||||||
@@ -121,6 +132,14 @@ class NavButtons : XposedModule() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AOSP_LAUNCHER_PACKAGE == packageName) {
|
||||||
|
if (aospTaskbarHookInstalled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initContextAndHandler()
|
||||||
|
aospTaskbarHookInstalled = hookAospTaskbar(classLoader)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initContextAndHandler() {
|
private fun initContextAndHandler() {
|
||||||
@@ -349,6 +368,18 @@ class NavButtons : XposedModule() {
|
|||||||
Long::class.javaPrimitiveType!!,
|
Long::class.javaPrimitiveType!!,
|
||||||
Int::class.javaPrimitiveType!!,
|
Int::class.javaPrimitiveType!!,
|
||||||
)
|
)
|
||||||
|
val systemReadyMethod = findMethodIfExists(phoneWindowManagerClass, "systemReady")
|
||||||
|
|
||||||
|
if (systemReadyMethod != null) {
|
||||||
|
deoptimizeMethodIfPresent(systemReadyMethod)
|
||||||
|
hook(systemReadyMethod).intercept { chain ->
|
||||||
|
val result = chain.proceed()
|
||||||
|
updateContextFromField(chain.thisObject, "mContext")
|
||||||
|
registerPrivilegedActionReceiver()
|
||||||
|
result
|
||||||
|
}
|
||||||
|
installed = true
|
||||||
|
}
|
||||||
|
|
||||||
if (longPressOnHomeWithEvent != null) {
|
if (longPressOnHomeWithEvent != null) {
|
||||||
deoptimizeMethodIfPresent(longPressOnHomeWithEvent)
|
deoptimizeMethodIfPresent(longPressOnHomeWithEvent)
|
||||||
@@ -400,6 +431,253 @@ class NavButtons : XposedModule() {
|
|||||||
return installed
|
return installed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hookAospTaskbar(classLoader: ClassLoader?): Boolean {
|
||||||
|
val controllerClass = findClassIfExists(AOSP_TASKBAR_NAV_BUTTON_CONTROLLER, classLoader) ?: run {
|
||||||
|
log("AOSP taskbar controller class not found: $AOSP_TASKBAR_NAV_BUTTON_CONTROLLER")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val navButtonsViewControllerClass = findClassIfExists(
|
||||||
|
AOSP_NAVBAR_BUTTONS_VIEW_CONTROLLER,
|
||||||
|
classLoader,
|
||||||
|
)
|
||||||
|
val clickMethod = findMethodIfExists(
|
||||||
|
controllerClass,
|
||||||
|
"onButtonClick",
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
View::class.java,
|
||||||
|
) ?: run {
|
||||||
|
log("AOSP taskbar onButtonClick() method not found.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val longClickMethod = findMethodIfExists(
|
||||||
|
controllerClass,
|
||||||
|
"onButtonLongClick",
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
View::class.java,
|
||||||
|
) ?: run {
|
||||||
|
log("AOSP taskbar onButtonLongClick() method not found.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val sendBackKeyEventMethod = findMethodIfExists(
|
||||||
|
controllerClass,
|
||||||
|
"sendBackKeyEvent",
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
Boolean::class.javaPrimitiveType!!,
|
||||||
|
) ?: run {
|
||||||
|
log("AOSP taskbar sendBackKeyEvent() method not found.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
deoptimizeMethodIfPresent(clickMethod)
|
||||||
|
deoptimizeMethodIfPresent(longClickMethod)
|
||||||
|
deoptimizeMethodIfPresent(sendBackKeyEventMethod)
|
||||||
|
hookAospTaskbarButtonMapping(navButtonsViewControllerClass, controllerClass)
|
||||||
|
hookAospTaskbarTouchEvents()
|
||||||
|
|
||||||
|
hook(clickMethod).intercept { chain ->
|
||||||
|
val buttonId = buttonIdForAospTaskbarCode(chain.args[0] as? Int)
|
||||||
|
?: return@intercept chain.proceed()
|
||||||
|
val pressAction = getButtonConfig(buttonId).pressAction
|
||||||
|
if (pressAction == NavButtonAction.STOCK) {
|
||||||
|
return@intercept chain.proceed()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContextFromTaskbar(chain.thisObject, chain.args.getOrNull(1))
|
||||||
|
performAospTaskbarAction(chain.thisObject, pressAction)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
hook(longClickMethod).intercept { chain ->
|
||||||
|
val buttonId = buttonIdForAospTaskbarCode(chain.args[0] as? Int)
|
||||||
|
?: return@intercept chain.proceed()
|
||||||
|
val longPressAction = getButtonConfig(buttonId).longPressAction
|
||||||
|
if (longPressAction == NavButtonAction.STOCK) {
|
||||||
|
return@intercept chain.proceed()
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = chain.args.getOrNull(1)
|
||||||
|
if (view != null && aospTaskbarButtonIds.containsKey(view)) {
|
||||||
|
return@intercept true
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContextFromTaskbar(chain.thisObject, chain.args.getOrNull(1))
|
||||||
|
if (buttonId == NavButtonId.HOME) {
|
||||||
|
performAospTaskbarHomeLongPressAction(chain.thisObject, longPressAction)
|
||||||
|
} else {
|
||||||
|
performAospTaskbarAction(chain.thisObject, longPressAction)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
hook(sendBackKeyEventMethod).intercept { chain ->
|
||||||
|
val pressAction = getButtonConfig(NavButtonId.BACK).pressAction
|
||||||
|
val longPressAction = getButtonConfig(NavButtonId.BACK).longPressAction
|
||||||
|
val action = chain.args[0] as? Int ?: return@intercept chain.proceed()
|
||||||
|
val canceled = chain.args.getOrNull(1) as? Boolean ?: false
|
||||||
|
val customLongPress = longPressAction != NavButtonAction.STOCK
|
||||||
|
val longPressTriggered = aospTaskbarBackLongPressTriggered[chain.thisObject] == true
|
||||||
|
|
||||||
|
if (pressAction == NavButtonAction.STOCK && !customLongPress) {
|
||||||
|
return@intercept chain.proceed()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContextFromField(chain.thisObject, "mContext")
|
||||||
|
|
||||||
|
if (action == KeyEvent.ACTION_UP) {
|
||||||
|
aospTaskbarBackLongPressTriggered.remove(chain.thisObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customLongPress && action == KeyEvent.ACTION_DOWN) {
|
||||||
|
return@intercept null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == KeyEvent.ACTION_UP && !canceled && !longPressTriggered) {
|
||||||
|
performAospTaskbarAction(
|
||||||
|
chain.thisObject,
|
||||||
|
if (pressAction == NavButtonAction.STOCK) NavButtonAction.BACK else pressAction,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hookAospTaskbarButtonMapping(
|
||||||
|
navButtonsViewControllerClass: Class<*>?,
|
||||||
|
controllerClass: Class<*>,
|
||||||
|
) {
|
||||||
|
if (navButtonsViewControllerClass == null) {
|
||||||
|
log("AOSP navbar buttons controller class not found: $AOSP_NAVBAR_BUTTONS_VIEW_CONTROLLER")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
arrayOf(
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
ViewGroup::class.java,
|
||||||
|
controllerClass,
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
),
|
||||||
|
arrayOf(
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
ViewGroup::class.java,
|
||||||
|
controllerClass,
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
Int::class.javaPrimitiveType!!,
|
||||||
|
),
|
||||||
|
).forEach { parameterTypes ->
|
||||||
|
val addButtonMethod = findMethodIfExists(
|
||||||
|
navButtonsViewControllerClass,
|
||||||
|
"addButton",
|
||||||
|
*parameterTypes,
|
||||||
|
) ?: return@forEach
|
||||||
|
deoptimizeMethodIfPresent(addButtonMethod)
|
||||||
|
hook(addButtonMethod).intercept { chain ->
|
||||||
|
val result = chain.proceed()
|
||||||
|
val view = result as? View ?: return@intercept result
|
||||||
|
trackAospTaskbarButton(
|
||||||
|
view,
|
||||||
|
chain.args.getOrNull(1) as? Int,
|
||||||
|
chain.args.getOrNull(3),
|
||||||
|
)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hookAospTaskbarTouchEvents() {
|
||||||
|
val onTouchEventMethod = findMethodIfExists(
|
||||||
|
View::class.java,
|
||||||
|
"onTouchEvent",
|
||||||
|
MotionEvent::class.java,
|
||||||
|
) ?: run {
|
||||||
|
log("AOSP taskbar View.onTouchEvent() method not found.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deoptimizeMethodIfPresent(onTouchEventMethod)
|
||||||
|
hook(onTouchEventMethod).intercept { chain ->
|
||||||
|
val view = chain.thisObject as? View ?: return@intercept chain.proceed()
|
||||||
|
val event = chain.args.getOrNull(0) as? MotionEvent ?: return@intercept chain.proceed()
|
||||||
|
val overrideResult = handleAospTaskbarTouch(view, event)
|
||||||
|
overrideResult ?: chain.proceed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun trackAospTaskbarButton(view: View, buttonCode: Int?, controller: Any?) {
|
||||||
|
val buttonId = buttonIdForAospTaskbarCode(buttonCode) ?: return
|
||||||
|
aospTaskbarButtonIds[view] = buttonId
|
||||||
|
if (controller != null) {
|
||||||
|
aospTaskbarControllers[view] = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAospTaskbarTouch(view: View, event: MotionEvent): Boolean? {
|
||||||
|
val buttonId = aospTaskbarButtonIds[view] ?: return null
|
||||||
|
val controller = aospTaskbarControllers[view] ?: return null
|
||||||
|
val config = getButtonConfig(buttonId)
|
||||||
|
if (config.longPressAction == NavButtonAction.STOCK) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
when (event.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
aospTaskbarLongPressTriggered[view] = false
|
||||||
|
if (buttonId == NavButtonId.BACK) {
|
||||||
|
aospTaskbarBackLongPressTriggered[controller] = false
|
||||||
|
}
|
||||||
|
scheduleAospTaskbarLongPress(view, controller, buttonId, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP,
|
||||||
|
MotionEvent.ACTION_CANCEL,
|
||||||
|
-> {
|
||||||
|
val triggered = aospTaskbarLongPressTriggered[view] == true
|
||||||
|
cancelAospTaskbarLongPress(view)
|
||||||
|
aospTaskbarLongPressTriggered.remove(view)
|
||||||
|
if (event.actionMasked == MotionEvent.ACTION_CANCEL && buttonId == NavButtonId.BACK) {
|
||||||
|
aospTaskbarBackLongPressTriggered.remove(controller)
|
||||||
|
}
|
||||||
|
if (triggered) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleAospTaskbarLongPress(
|
||||||
|
view: View,
|
||||||
|
controller: Any,
|
||||||
|
buttonId: NavButtonId,
|
||||||
|
config: NavButtonConfig,
|
||||||
|
) {
|
||||||
|
cancelAospTaskbarLongPress(view)
|
||||||
|
val longPressRunnable = Runnable {
|
||||||
|
aospTaskbarLongPressTriggered[view] = true
|
||||||
|
if (buttonId == NavButtonId.BACK) {
|
||||||
|
aospTaskbarBackLongPressTriggered[controller] = true
|
||||||
|
}
|
||||||
|
updateContextFromTaskbar(controller, view)
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
if (buttonId == NavButtonId.HOME) {
|
||||||
|
performAospTaskbarHomeLongPressAction(controller, config.longPressAction)
|
||||||
|
} else {
|
||||||
|
performAospTaskbarAction(controller, config.longPressAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aospTaskbarLongPressRunnables[view] = longPressRunnable
|
||||||
|
view.postDelayed(longPressRunnable, config.longPressDurationMs.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelAospTaskbarLongPress(view: View) {
|
||||||
|
aospTaskbarLongPressRunnables.remove(view)?.let(view::removeCallbacks)
|
||||||
|
}
|
||||||
|
|
||||||
private fun hookHoneyspaceTaskbar(classLoader: ClassLoader?): Boolean {
|
private fun hookHoneyspaceTaskbar(classLoader: ClassLoader?): Boolean {
|
||||||
val navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader) ?: run {
|
val navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader) ?: run {
|
||||||
log("Launcher class not found: $HONEYSPACE_NAV_BUTTON_VIEW")
|
log("Launcher class not found: $HONEYSPACE_NAV_BUTTON_VIEW")
|
||||||
@@ -937,6 +1215,14 @@ class NavButtons : XposedModule() {
|
|||||||
context = currentContext.applicationContext ?: currentContext
|
context = currentContext.applicationContext ?: currentContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateContextFromTaskbar(controller: Any?, view: Any?) {
|
||||||
|
if (view is View) {
|
||||||
|
updateContextFromView(view)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateContextFromField(controller, "mContext")
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateContextFromField(target: Any?, fieldName: String) {
|
private fun updateContextFromField(target: Any?, fieldName: String) {
|
||||||
val currentContext = getFieldValue(target, fieldName) as? Context ?: return
|
val currentContext = getFieldValue(target, fieldName) as? Context ?: return
|
||||||
context = currentContext.applicationContext ?: currentContext
|
context = currentContext.applicationContext ?: currentContext
|
||||||
@@ -958,6 +1244,70 @@ class NavButtons : XposedModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun performAospTaskbarHomeLongPressAction(controller: Any?, action: NavButtonAction) {
|
||||||
|
if (wasHomeLongPressHandledRecently()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
noteHomeLongPressHandled()
|
||||||
|
performAospTaskbarAction(controller, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun performAospTaskbarAction(controller: Any?, action: NavButtonAction) {
|
||||||
|
when (action) {
|
||||||
|
NavButtonAction.STOCK,
|
||||||
|
NavButtonAction.NONE,
|
||||||
|
-> Unit
|
||||||
|
|
||||||
|
NavButtonAction.BACK -> {
|
||||||
|
if (!callAospTaskbarMethod(controller, "executeBack", arrayOf(KeyEvent::class.java), null)) {
|
||||||
|
performConfiguredAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavButtonAction.HOME -> {
|
||||||
|
if (!callAospTaskbarMethod(controller, "navigateHome", emptyArray())) {
|
||||||
|
performConfiguredAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavButtonAction.ASSISTANT -> {
|
||||||
|
if (!callAospTaskbarMethod(controller, "onLongPressHome", emptyArray())) {
|
||||||
|
performConfiguredAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavButtonAction.RECENTS -> {
|
||||||
|
if (!callAospTaskbarMethod(controller, "navigateToOverview", emptyArray())) {
|
||||||
|
performConfiguredAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavButtonAction.KILL_FOREGROUND_APP -> requestPrivilegedAction(PRIVILEGED_ACTION_KILL_FOREGROUND_APP)
|
||||||
|
|
||||||
|
NavButtonAction.TOGGLE_FLASHLIGHT -> requestPrivilegedAction(PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT)
|
||||||
|
|
||||||
|
NavButtonAction.TOGGLE_AUTO_ROTATE -> performConfiguredAction(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callAospTaskbarMethod(
|
||||||
|
controller: Any?,
|
||||||
|
methodName: String,
|
||||||
|
parameterTypes: Array<Class<*>>,
|
||||||
|
vararg args: Any?,
|
||||||
|
): Boolean {
|
||||||
|
if (controller == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
callMethod(controller, methodName, parameterTypes, *args)
|
||||||
|
true
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
log("AOSP taskbar $methodName failed: ${error.javaClass.simpleName}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendKeyPress(keyCode: Int) {
|
private fun sendKeyPress(keyCode: Int) {
|
||||||
try {
|
try {
|
||||||
val inputManager = callStaticMethod(
|
val inputManager = callStaticMethod(
|
||||||
@@ -1316,6 +1666,9 @@ class NavButtons : XposedModule() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val currentContext = context ?: return
|
val currentContext = context ?: return
|
||||||
|
if (currentContext.packageName == ANDROID_PACKAGE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val receiver = object : BroadcastReceiver() {
|
val receiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
@@ -1343,6 +1696,115 @@ class NavButtons : XposedModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun registerPrivilegedActionReceiver() {
|
||||||
|
if (privilegedActionReceiverRegistered) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentContext = context ?: getCurrentApplication() ?: return
|
||||||
|
context = currentContext.applicationContext ?: currentContext
|
||||||
|
|
||||||
|
val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (intent?.action != ACTION_PRIVILEGED_ACTION ||
|
||||||
|
context == null ||
|
||||||
|
!isAllowedPrivilegedActionSender(
|
||||||
|
context,
|
||||||
|
intent,
|
||||||
|
getSentFromPackageCompat(),
|
||||||
|
getSentFromUidCompat(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this@NavButtons.context = context.applicationContext ?: context
|
||||||
|
when (intent.getStringExtra(EXTRA_PRIVILEGED_ACTION)) {
|
||||||
|
PRIVILEGED_ACTION_KILL_FOREGROUND_APP -> killForegroundApp()
|
||||||
|
PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT -> toggleFlashlight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentContext.registerReceiver(
|
||||||
|
receiver,
|
||||||
|
IntentFilter(ACTION_PRIVILEGED_ACTION),
|
||||||
|
Context.RECEIVER_EXPORTED,
|
||||||
|
)
|
||||||
|
privilegedActionToken = readPrivilegedActionToken()
|
||||||
|
privilegedActionReceiverRegistered = true
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
log("Privileged action receiver failed", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BroadcastReceiver.getSentFromUidCompat(): Int {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
sentFromUid
|
||||||
|
} else {
|
||||||
|
Process.INVALID_UID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BroadcastReceiver.getSentFromPackageCompat(): String? {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
sentFromPackage
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAllowedPrivilegedActionSender(
|
||||||
|
context: Context,
|
||||||
|
intent: Intent,
|
||||||
|
senderPackage: String?,
|
||||||
|
senderUid: Int,
|
||||||
|
): Boolean {
|
||||||
|
if (senderPackage == AOSP_LAUNCHER_PACKAGE) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val expectedToken = privilegedActionToken ?: readPrivilegedActionToken()?.also {
|
||||||
|
privilegedActionToken = it
|
||||||
|
}
|
||||||
|
if (expectedToken != null &&
|
||||||
|
expectedToken == intent.getStringExtra(EXTRA_PRIVILEGED_ACTION_TOKEN)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senderUid == Process.SYSTEM_UID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return context.packageManager.getPackagesForUid(senderUid)
|
||||||
|
?.contains(AOSP_LAUNCHER_PACKAGE) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readPrivilegedActionToken(): String? {
|
||||||
|
return remotePreferences?.getString(NavButtonSettingsStore.KEY_PRIVILEGED_ACTION_TOKEN, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestPrivilegedAction(action: String) {
|
||||||
|
val currentContext = context
|
||||||
|
if (currentContext == null) {
|
||||||
|
when (action) {
|
||||||
|
PRIVILEGED_ACTION_KILL_FOREGROUND_APP -> killForegroundApp()
|
||||||
|
PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT -> toggleFlashlight()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val token = remotePreferences?.getString(
|
||||||
|
NavButtonSettingsStore.KEY_PRIVILEGED_ACTION_TOKEN,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
val intent = Intent(ACTION_PRIVILEGED_ACTION)
|
||||||
|
.setPackage(ANDROID_PACKAGE)
|
||||||
|
.putExtra(EXTRA_PRIVILEGED_ACTION, action)
|
||||||
|
if (token != null) {
|
||||||
|
intent.putExtra(EXTRA_PRIVILEGED_ACTION_TOKEN, token)
|
||||||
|
}
|
||||||
|
currentContext.sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun trackSamsungNavigationBarView(startView: Any) {
|
private fun trackSamsungNavigationBarView(startView: Any) {
|
||||||
var current: View? = startView as? View ?: return
|
var current: View? = startView as? View ?: return
|
||||||
repeat(SAMSUNG_NAV_VIEW_PARENT_DEPTH) {
|
repeat(SAMSUNG_NAV_VIEW_PARENT_DEPTH) {
|
||||||
@@ -1766,6 +2228,15 @@ class NavButtons : XposedModule() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buttonIdForAospTaskbarCode(buttonCode: Int?): NavButtonId? {
|
||||||
|
return when (buttonCode) {
|
||||||
|
AOSP_TASKBAR_BACK_BUTTON -> NavButtonId.BACK
|
||||||
|
AOSP_TASKBAR_HOME_BUTTON -> NavButtonId.HOME
|
||||||
|
AOSP_TASKBAR_RECENTS_BUTTON -> NavButtonId.RECENTS
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPackageNameFromComponent(component: Any?): String? {
|
private fun getPackageNameFromComponent(component: Any?): String? {
|
||||||
return (component as? ComponentName)?.packageName
|
return (component as? ComponentName)?.packageName
|
||||||
}
|
}
|
||||||
@@ -1785,6 +2256,7 @@ class NavButtons : XposedModule() {
|
|||||||
return packageName == null ||
|
return packageName == null ||
|
||||||
packageName == SYSTEMUI_PACKAGE ||
|
packageName == SYSTEMUI_PACKAGE ||
|
||||||
packageName == SAMSUNG_LAUNCHER_PACKAGE ||
|
packageName == SAMSUNG_LAUNCHER_PACKAGE ||
|
||||||
|
packageName == AOSP_LAUNCHER_PACKAGE ||
|
||||||
packageName == defaultLauncher
|
packageName == defaultLauncher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2316,6 +2788,7 @@ class NavButtons : XposedModule() {
|
|||||||
const val STOCK_LONG_PRESS_GUARD_EARLY_MS = 75
|
const val STOCK_LONG_PRESS_GUARD_EARLY_MS = 75
|
||||||
const val STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS = 100
|
const val STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS = 100
|
||||||
const val SYSTEMUI_PACKAGE = "com.android.systemui"
|
const val SYSTEMUI_PACKAGE = "com.android.systemui"
|
||||||
|
const val ANDROID_PACKAGE = "android"
|
||||||
const val SYSTEM_SERVER_PHONE_WINDOW_MANAGER = "com.android.server.policy.PhoneWindowManager"
|
const val SYSTEM_SERVER_PHONE_WINDOW_MANAGER = "com.android.server.policy.PhoneWindowManager"
|
||||||
const val SYSTEMUI_KEY_BUTTON_VIEW =
|
const val SYSTEMUI_KEY_BUTTON_VIEW =
|
||||||
"com.android.systemui.navigationbar.views.buttons.KeyButtonView"
|
"com.android.systemui.navigationbar.views.buttons.KeyButtonView"
|
||||||
@@ -2326,6 +2799,20 @@ class NavButtons : XposedModule() {
|
|||||||
const val SAMSUNG_NAVIGATION_BAR_VIEW =
|
const val SAMSUNG_NAVIGATION_BAR_VIEW =
|
||||||
"com.android.systemui.navigationbar.views.SamsungNavigationBarView"
|
"com.android.systemui.navigationbar.views.SamsungNavigationBarView"
|
||||||
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
|
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
|
||||||
|
const val AOSP_LAUNCHER_PACKAGE = "com.android.launcher3"
|
||||||
|
const val AOSP_TASKBAR_NAV_BUTTON_CONTROLLER =
|
||||||
|
"com.android.launcher3.taskbar.TaskbarNavButtonController"
|
||||||
|
const val AOSP_TASKBAR_BACK_BUTTON = 1
|
||||||
|
const val AOSP_TASKBAR_HOME_BUTTON = 2
|
||||||
|
const val AOSP_TASKBAR_RECENTS_BUTTON = 4
|
||||||
|
const val AOSP_NAVBAR_BUTTONS_VIEW_CONTROLLER =
|
||||||
|
"com.android.launcher3.taskbar.NavbarButtonsViewController"
|
||||||
|
const val ACTION_PRIVILEGED_ACTION =
|
||||||
|
"se.ajpanton.navbuttons.action.PRIVILEGED_ACTION"
|
||||||
|
const val EXTRA_PRIVILEGED_ACTION = "privileged_action"
|
||||||
|
const val EXTRA_PRIVILEGED_ACTION_TOKEN = "privileged_action_token"
|
||||||
|
const val PRIVILEGED_ACTION_KILL_FOREGROUND_APP = "kill_foreground_app"
|
||||||
|
const val PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT = "toggle_flashlight"
|
||||||
const val SIDE_ARROW_ICON_DP = 32f
|
const val SIDE_ARROW_ICON_DP = 32f
|
||||||
const val SIDE_ARROW_GLYPH_SCALE = 0.8f
|
const val SIDE_ARROW_GLYPH_SCALE = 0.8f
|
||||||
const val SIDE_ARROW_STROKE_WIDTH = 1.45f
|
const val SIDE_ARROW_STROKE_WIDTH = 1.45f
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
android
|
android
|
||||||
|
system
|
||||||
com.android.systemui
|
com.android.systemui
|
||||||
|
com.android.launcher3
|
||||||
com.sec.android.app.launcher
|
com.sec.android.app.launcher
|
||||||
com.samsung.systemui.navillera
|
com.samsung.systemui.navillera
|
||||||
|
|||||||
Reference in New Issue
Block a user