diff --git a/app/src/main/java/se/ajpanton/navbuttons/AospLauncherTaskbarHooks.kt b/app/src/main/java/se/ajpanton/navbuttons/AospLauncherTaskbarHooks.kt index 76b1f6c..d9b60ab 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/AospLauncherTaskbarHooks.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/AospLauncherTaskbarHooks.kt @@ -394,44 +394,40 @@ internal class AospLauncherTaskbarHooks( } private fun performAction(controller: Any?, action: NavButtonAction) { - when (action) { + val handled = when (action) { NavButtonAction.STOCK, NavButtonAction.NONE, - -> Unit + -> true - NavButtonAction.BACK -> { - if (!callTaskbarMethod(controller, "executeBack", arrayOf(KeyEvent::class.java), null)) { - actions.perform(action) - } - } + NavButtonAction.BACK -> + callTaskbarMethod(controller, "executeBack", arrayOf(KeyEvent::class.java), null) - NavButtonAction.HOME -> { - if (!callTaskbarMethod(controller, "navigateHome", emptyArray())) { - actions.perform(action) - } - } + NavButtonAction.HOME -> + callTaskbarMethod(controller, "navigateHome", emptyArray()) - NavButtonAction.ASSISTANT -> { - if (!callTaskbarMethod(controller, "onLongPressHome", emptyArray())) { - actions.perform(action) - } - } + NavButtonAction.ASSISTANT -> + callTaskbarMethod(controller, "onLongPressHome", emptyArray()) - NavButtonAction.RECENTS -> { - if (!callTaskbarMethod(controller, "navigateToOverview", emptyArray())) { - actions.perform(action) - } - } + NavButtonAction.RECENTS -> + callTaskbarMethod(controller, "navigateToOverview", emptyArray()) NavButtonAction.KILL_FOREGROUND_APP -> { requestPrivilegedAction(PRIVILEGED_ACTION_KILL_FOREGROUND_APP, null) + true } NavButtonAction.TOGGLE_FLASHLIGHT -> { requestPrivilegedAction(PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT, null) + true } - NavButtonAction.TOGGLE_AUTO_ROTATE -> actions.perform(action) + NavButtonAction.TOGGLE_AUTO_ROTATE -> { + actions.perform(action) + true + } + } + if (!handled) { + actions.perform(action) } } diff --git a/app/src/main/java/se/ajpanton/navbuttons/ReflectionUtils.kt b/app/src/main/java/se/ajpanton/navbuttons/ReflectionUtils.kt index a92f0a1..2ab1969 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/ReflectionUtils.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/ReflectionUtils.kt @@ -126,58 +126,29 @@ internal fun callIntIntMethodIfExists( 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 - } - } + optionalMethod(target, methodName) { types -> + types.size >= 2 && types[0] == Int::class.javaPrimitiveType && types[1] == Int::class.javaPrimitiveType + }?.let { method -> return invokeOptional(method, target, first, second) } } return false } internal fun callIntMethodIfExists(target: Any, methodName: String, value: Int): Boolean { - return findMethodsNamed(target.javaClass, methodName) - .firstOrNull { method -> - val types = method.parameterTypes - types.size == 1 && types[0] == Int::class.javaPrimitiveType - } - ?.let { method -> - runCatching { method.invoke(target, value) }.isSuccess - } == true + return invokeOptional(target, methodName, value) { types -> + types.size == 1 && types[0] == Int::class.javaPrimitiveType + } } internal fun callIntIntMethodIfExists(target: Any, methodName: String, first: Int, second: Int): Boolean { - return 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 -> - runCatching { method.invoke(target, first, second) }.isSuccess - } == true + return invokeOptional(target, methodName, first, second) { types -> + types.size == 2 && types[0] == Int::class.javaPrimitiveType && types[1] == Int::class.javaPrimitiveType + } } internal fun callFloatMethodIfExists(target: Any, methodName: String, value: Float): Boolean { - return findMethodsNamed(target.javaClass, methodName) - .firstOrNull { method -> - val types = method.parameterTypes - types.size == 1 && types[0] == Float::class.javaPrimitiveType - } - ?.let { method -> - runCatching { method.invoke(target, value) }.isSuccess - } == true + return invokeOptional(target, methodName, value) { types -> + types.size == 1 && types[0] == Float::class.javaPrimitiveType + } } internal fun callSetButtonImageIfExists( @@ -186,36 +157,47 @@ internal fun callSetButtonImageIfExists( lightDrawable: Drawable, darkDrawable: Drawable, ): Boolean { - return findMethodsNamed(target.javaClass, "setButtonImage") - .firstOrNull { method -> - val types = method.parameterTypes - types.size == 3 && - types[0] == Int::class.javaPrimitiveType && - Drawable::class.java.isAssignableFrom(types[1]) && - Drawable::class.java.isAssignableFrom(types[2]) - } - ?.let { method -> - runCatching { method.invoke(target, id, lightDrawable, darkDrawable) }.isSuccess - } == true + return invokeOptional(target, "setButtonImage", id, lightDrawable, darkDrawable) { types -> + types.size == 3 && + types[0] == Int::class.javaPrimitiveType && + Drawable::class.java.isAssignableFrom(types[1]) && + Drawable::class.java.isAssignableFrom(types[2]) + } } internal fun callMethodIfExists(target: Any, methodName: String): Any? { - return findMethodsNamed(target.javaClass, methodName) - .firstOrNull { method -> method.parameterTypes.isEmpty() } + return optionalMethod(target, methodName) { it.isEmpty() } ?.let { method -> runCatching { method.invoke(target) }.getOrNull() } } internal fun callBooleanMethodIfExists(target: Any, methodName: String, value: Boolean): Boolean { - return findMethodsNamed(target.javaClass, methodName) - .firstOrNull { method -> - val types = method.parameterTypes - types.size == 1 && types[0] == Boolean::class.javaPrimitiveType - } - ?.let { method -> - runCatching { method.invoke(target, value) }.isSuccess - } ?: false + return invokeOptional(target, methodName, value) { types -> + types.size == 1 && types[0] == Boolean::class.javaPrimitiveType + } +} + +private fun optionalMethod( + target: Any, + methodName: String, + accepts: (Array>) -> Boolean, +): Method? { + return findMethodsNamed(target.javaClass, methodName).firstOrNull { accepts(it.parameterTypes) } +} + +private fun invokeOptional( + target: Any, + methodName: String, + vararg args: Any?, + accepts: (Array>) -> Boolean, +): Boolean { + val method = optionalMethod(target, methodName, accepts) ?: return false + return invokeOptional(method, target, *args) +} + +private fun invokeOptional(method: Method, target: Any, vararg args: Any?): Boolean { + return runCatching { method.invoke(target, *args) }.isSuccess } internal fun getFieldValue(target: Any?, fieldName: String): Any? { diff --git a/app/src/main/java/se/ajpanton/navbuttons/SettingsActivity.kt b/app/src/main/java/se/ajpanton/navbuttons/SettingsActivity.kt index 895aa56..a29d9ec 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/SettingsActivity.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/SettingsActivity.kt @@ -224,6 +224,77 @@ class SettingsActivity : Activity() { } } + private fun EditText.afterTextChanged(onChanged: (String) -> Unit) { + addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + override fun afterTextChanged(editable: Editable?) = onChanged(editable?.toString().orEmpty()) + }) + } + + private inner class NumericField( + private val input: EditText, + private val minusButton: Button, + private val plusButton: Button, + private val step: Float, + private val parse: (String) -> Float?, + private val sanitize: (Float) -> Float, + private val format: (Float) -> String, + private val persist: (Float) -> Unit, + ) { + private var lastValid = 0f + private var updatingText = false + + fun bind(initialValue: Float) { + apply(initialValue, persist = false) + input.setOnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + normalize() + } + } + input.afterTextChanged { text -> + if (updatingText) { + return@afterTextChanged + } + val parsed = parse(text) ?: return@afterTextChanged + if (parsed == sanitize(parsed)) { + lastValid = parsed + persist(parsed) + sendSettingsChangedBroadcast() + } + } + attachRepeatButton(minusButton) { adjust(-step) } + attachRepeatButton(plusButton) { adjust(step) } + } + + fun normalize(): Float { + val normalized = sanitize(parse(input.text.toString()) ?: lastValid) + apply(normalized, persist = true) + return normalized + } + + private fun adjust(delta: Float) { + apply(normalize() + delta, persist = true) + } + + private fun apply(value: Float, persist: Boolean) { + val sanitized = sanitize(value) + val text = format(sanitized) + updatingText = true + if (input.text.toString() != text) { + input.setText(text) + input.setSelection(input.text.length) + } + updatingText = false + + lastValid = sanitized + if (persist) { + persist(sanitized) + sendSettingsChangedBroadcast() + } + } + } + private inner class NavButtonSectionController( sectionView: View, private val buttonId: NavButtonId, @@ -237,8 +308,7 @@ class SettingsActivity : Activity() { private val plusButton: Button = sectionView.findViewById(R.id.plus_button) private val testButton: Button = sectionView.findViewById(R.id.test_button) - private var lastValidDurationMs = settingsStore.defaultLongPressDurationMs() - private var updatingDurationText = false + private lateinit var durationField: NumericField fun bind() { val config = settingsStore.getConfig(buttonId) @@ -255,10 +325,14 @@ class SettingsActivity : Activity() { settingsStore::setLongPressAction, ) - applyDuration(config.longPressDurationMs, persist = false) - bindDurationInput() - bindStepButtons() - attachTestButton(testButton, ::resolveDurationForUse) + durationField = durationField( + durationInput, + minusButton, + plusButton, + persist = { settingsStore.setLongPressDurationMs(buttonId, it) }, + ) + durationField.bind(config.longPressDurationMs.toFloat()) + attachTestButton(testButton) { durationField.normalize().toInt() } } private fun bindActionSpinner( @@ -276,89 +350,6 @@ class SettingsActivity : Activity() { sendSettingsChangedBroadcast() } } - - private fun bindDurationInput() { - durationInput.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - normalizeTypedDuration() - } - } - - durationInput.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged( - s: CharSequence?, - start: Int, - count: Int, - after: Int, - ) = Unit - - override fun onTextChanged( - s: CharSequence?, - start: Int, - before: Int, - count: Int, - ) = Unit - - override fun afterTextChanged(editable: Editable?) { - if (updatingDurationText) { - return - } - - val parsed = editable?.toString()?.toIntOrNull() ?: return - if (parsed in NavButtonSettingsStore.MIN_DURATION_MS..NavButtonSettingsStore.MAX_DURATION_MS) { - lastValidDurationMs = parsed - settingsStore.setLongPressDurationMs(buttonId, parsed) - sendSettingsChangedBroadcast() - } - } - }) - } - - private fun bindStepButtons() { - attachRepeatButton(minusButton) { - adjustDuration(-NavButtonSettingsStore.DURATION_STEP_MS) - } - attachRepeatButton(plusButton) { - adjustDuration(NavButtonSettingsStore.DURATION_STEP_MS) - } - } - - private fun adjustDuration(deltaMs: Int) { - val current = resolveDurationForUse() - val updated = (current + deltaMs).coerceIn( - NavButtonSettingsStore.MIN_DURATION_MS, - NavButtonSettingsStore.MAX_DURATION_MS, - ) - applyDuration(updated, persist = true) - } - - private fun normalizeTypedDuration(): Int { - val parsed = durationInput.text.toString().toIntOrNull() ?: lastValidDurationMs - val normalized = settingsStore.sanitizeDuration(parsed) - applyDuration(normalized, persist = true) - return normalized - } - - private fun resolveDurationForUse(): Int { - return normalizeTypedDuration() - } - - private fun applyDuration(durationMs: Int, persist: Boolean) { - val sanitized = settingsStore.sanitizeDuration(durationMs) - val text = sanitized.toString() - updatingDurationText = true - if (durationInput.text.toString() != text) { - durationInput.setText(text) - durationInput.setSelection(durationInput.text.length) - } - updatingDurationText = false - - lastValidDurationMs = sanitized - if (persist) { - settingsStore.setLongPressDurationMs(buttonId, sanitized) - sendSettingsChangedBroadcast() - } - } } private inner class SideArrowsSectionController(sectionView: View) { @@ -384,21 +375,31 @@ class SettingsActivity : Activity() { private val testButton: Button = sectionView.findViewById(R.id.side_arrow_test_button) private val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator - private var lastValidDurationMs = settingsStore.defaultLongPressDurationMs() - private var lastValidRepeatSpeed = NavButtonSettingsStore.DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND - private var updatingDurationText = false - private var updatingRepeatSpeedText = false + private lateinit var durationField: NumericField + private lateinit var repeatSpeedField: NumericField fun bind() { sideArrowsCheckbox.isChecked = settingsStore.isSideArrowsEnabled() bindLongPressActionSpinner() - applyRepeatSpeed(settingsStore.getSideArrowRepeatStepsPerSecond(), persist = false) - bindRepeatSpeedInput() - bindRepeatSpeedButtons() - applyDuration(settingsStore.getSideArrowLongPressDurationMs(), persist = false) - bindDurationInput() - bindDurationButtons() - attachTestButton(testButton, ::resolveDurationForUse) + repeatSpeedField = NumericField( + repeatSpeedInput, + speedMinusButton, + speedPlusButton, + NavButtonSettingsStore.SIDE_ARROW_REPEAT_SPEED_STEP, + parse = ::parseRepeatSpeed, + sanitize = settingsStore::sanitizeSideArrowRepeatSpeed, + format = ::formatRepeatSpeed, + persist = settingsStore::setSideArrowRepeatStepsPerSecond, + ) + repeatSpeedField.bind(settingsStore.getSideArrowRepeatStepsPerSecond()) + durationField = durationField( + durationInput, + durationMinusButton, + durationPlusButton, + persist = settingsStore::setSideArrowLongPressDurationMs, + ) + durationField.bind(settingsStore.getSideArrowLongPressDurationMs().toFloat()) + attachTestButton(testButton) { durationField.normalize().toInt() } updateOptionsEnabled() sideArrowsCheckbox.setOnCheckedChangeListener { _, isChecked -> @@ -422,100 +423,6 @@ class SettingsActivity : Activity() { updateRepeatSpeedVisibility(settingsStore.getSideArrowLongPressAction()) } - private fun bindRepeatSpeedInput() { - repeatSpeedInput.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - normalizeTypedRepeatSpeed() - } - } - - repeatSpeedInput.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged( - s: CharSequence?, - start: Int, - count: Int, - after: Int, - ) = Unit - - override fun onTextChanged( - s: CharSequence?, - start: Int, - before: Int, - count: Int, - ) = Unit - - override fun afterTextChanged(editable: Editable?) { - if (updatingRepeatSpeedText) { - return - } - - val parsed = parseRepeatSpeed(editable?.toString()) ?: return - if (parsed in NavButtonSettingsStore.MIN_SIDE_ARROW_REPEAT_STEPS_PER_SECOND.. - NavButtonSettingsStore.MAX_SIDE_ARROW_REPEAT_STEPS_PER_SECOND - ) { - lastValidRepeatSpeed = parsed - settingsStore.setSideArrowRepeatStepsPerSecond(parsed) - sendSettingsChangedBroadcast() - } - } - }) - } - - private fun bindRepeatSpeedButtons() { - attachRepeatButton(speedMinusButton) { - adjustRepeatSpeed(-NavButtonSettingsStore.SIDE_ARROW_REPEAT_SPEED_STEP) - } - attachRepeatButton(speedPlusButton) { - adjustRepeatSpeed(NavButtonSettingsStore.SIDE_ARROW_REPEAT_SPEED_STEP) - } - } - - private fun bindDurationInput() { - durationInput.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - normalizeTypedDuration() - } - } - - durationInput.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged( - s: CharSequence?, - start: Int, - count: Int, - after: Int, - ) = Unit - - override fun onTextChanged( - s: CharSequence?, - start: Int, - before: Int, - count: Int, - ) = Unit - - override fun afterTextChanged(editable: Editable?) { - if (updatingDurationText) { - return - } - - val parsed = editable?.toString()?.toIntOrNull() ?: return - if (parsed in NavButtonSettingsStore.MIN_DURATION_MS..NavButtonSettingsStore.MAX_DURATION_MS) { - lastValidDurationMs = parsed - settingsStore.setSideArrowLongPressDurationMs(parsed) - sendSettingsChangedBroadcast() - } - } - }) - } - - private fun bindDurationButtons() { - attachRepeatButton(durationMinusButton) { - adjustDuration(-NavButtonSettingsStore.DURATION_STEP_MS) - } - attachRepeatButton(durationPlusButton) { - adjustDuration(NavButtonSettingsStore.DURATION_STEP_MS) - } - } - private fun updateOptionsEnabled() { val enabled = sideArrowsCheckbox.isChecked optionsContainer.alpha = if (enabled) 1f else 0.45f @@ -536,38 +443,6 @@ class SettingsActivity : Activity() { if (action == SideArrowLongPressAction.MULTIPLE_STEPS) View.VISIBLE else View.GONE } - private fun adjustRepeatSpeed(delta: Float) { - applyRepeatSpeed(resolveRepeatSpeedForUse() + delta, persist = true) - } - - private fun normalizeTypedRepeatSpeed(): Float { - val parsed = parseRepeatSpeed(repeatSpeedInput.text.toString()) ?: lastValidRepeatSpeed - val normalized = settingsStore.sanitizeSideArrowRepeatSpeed(parsed) - applyRepeatSpeed(normalized, persist = true) - return normalized - } - - private fun resolveRepeatSpeedForUse(): Float { - return normalizeTypedRepeatSpeed() - } - - private fun applyRepeatSpeed(stepsPerSecond: Float, persist: Boolean) { - val sanitized = settingsStore.sanitizeSideArrowRepeatSpeed(stepsPerSecond) - val text = formatRepeatSpeed(sanitized) - updatingRepeatSpeedText = true - if (repeatSpeedInput.text.toString() != text) { - repeatSpeedInput.setText(text) - repeatSpeedInput.setSelection(repeatSpeedInput.text.length) - } - updatingRepeatSpeedText = false - - lastValidRepeatSpeed = sanitized - if (persist) { - settingsStore.setSideArrowRepeatStepsPerSecond(sanitized) - sendSettingsChangedBroadcast() - } - } - private fun parseRepeatSpeed(text: String?): Float? { return text ?.trim() @@ -586,42 +461,23 @@ class SettingsActivity : Activity() { return raw.replace('.', decimalSeparator) } - private fun adjustDuration(deltaMs: Int) { - val current = resolveDurationForUse() - val updated = (current + deltaMs).coerceIn( - NavButtonSettingsStore.MIN_DURATION_MS, - NavButtonSettingsStore.MAX_DURATION_MS, - ) - applyDuration(updated, persist = true) - } - - private fun normalizeTypedDuration(): Int { - val parsed = durationInput.text.toString().toIntOrNull() ?: lastValidDurationMs - val normalized = settingsStore.sanitizeDuration(parsed) - applyDuration(normalized, persist = true) - return normalized - } - - private fun resolveDurationForUse(): Int { - return normalizeTypedDuration() - } - - private fun applyDuration(durationMs: Int, persist: Boolean) { - val sanitized = settingsStore.sanitizeDuration(durationMs) - val text = sanitized.toString() - updatingDurationText = true - if (durationInput.text.toString() != text) { - durationInput.setText(text) - durationInput.setSelection(durationInput.text.length) - } - updatingDurationText = false - - lastValidDurationMs = sanitized - if (persist) { - settingsStore.setSideArrowLongPressDurationMs(sanitized) - sendSettingsChangedBroadcast() - } - } + } + private fun durationField( + input: EditText, + minusButton: Button, + plusButton: Button, + persist: (Int) -> Unit, + ): NumericField { + return NumericField( + input, + minusButton, + plusButton, + NavButtonSettingsStore.DURATION_STEP_MS.toFloat(), + parse = { it.toIntOrNull()?.toFloat() }, + sanitize = { settingsStore.sanitizeDuration(it.toInt()).toFloat() }, + format = { it.toInt().toString() }, + persist = { persist(it.toInt()) }, + ) } }