From 51ed03dd722f8c41be1e64af8485cc92e3ce8de5 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Thu, 20 Feb 2025 22:19:31 +0100 Subject: [PATCH] add logging to collect debugging data --- app/src/debug/res/values/donottranslate.xml | 2 +- .../jrpie/android/launcher/ui/HomeActivity.kt | 20 +++- .../launcher/ui/TouchGestureDetector.kt | 93 +++++++++++++++++-- app/src/main/res/layout/home.xml | 18 ++++ app/src/main/res/values/donottranslate.xml | 2 +- 5 files changed, 119 insertions(+), 16 deletions(-) diff --git a/app/src/debug/res/values/donottranslate.xml b/app/src/debug/res/values/donottranslate.xml index bf4f4e4..9edc514 100644 --- a/app/src/debug/res/values/donottranslate.xml +++ b/app/src/debug/res/values/donottranslate.xml @@ -1,3 +1,3 @@ - μLauncher [debug] + μLauncher [gesture detection debug] diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 973e0ca..331123d 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -16,6 +16,7 @@ import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction +import de.jrpie.android.launcher.copyToClipboard import de.jrpie.android.launcher.databinding.HomeBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity @@ -62,16 +63,19 @@ class HomeActivity : UIObject, AppCompatActivity() { val width = displayMetrics.widthPixels val height = displayMetrics.heightPixels + // Initialise layout + binding = HomeBinding.inflate(layoutInflater) + setContentView(binding.root) + + touchGestureDetector = TouchGestureDetector( this, width, height, LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f - ) - - // Initialise layout - binding = HomeBinding.inflate(layoutInflater) - setContentView(binding.root) + ) { s -> + binding.logOutput.text = s + } // Handle back key / gesture on Android 13+, cf. onKeyDown() @@ -86,6 +90,12 @@ class HomeActivity : UIObject, AppCompatActivity() { LauncherAction.SETTINGS.invoke(this) } + binding.buttonCopyLog.setOnClickListener { + copyToClipboard(this, + touchGestureDetector.log_output) + + } + } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt index 00629a5..c35fc22 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt @@ -1,20 +1,26 @@ package de.jrpie.android.launcher.ui import android.content.Context +import android.util.Log import android.view.MotionEvent import android.view.ViewConfiguration import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.preferences.LauncherPreferences +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import kotlin.math.abs import kotlin.math.max import kotlin.math.min +import kotlin.math.sqrt import kotlin.math.tan class TouchGestureDetector( private val context: Context, val width: Int, val height: Int, - var edgeWidth: Float + var edgeWidth: Float, + val onLog: (String) -> Unit ) { private val ANGULAR_THRESHOLD = tan(Math.PI / 6) private val TOUCH_SLOP: Int @@ -27,26 +33,51 @@ class TouchGestureDetector( private val MIN_TRIANGLE_HEIGHT = 250 + var log_output = "" + private var log_output_ui = "" + private fun log(s: String, ui:Boolean = true) { + Log.i("Gesture Detection", s) + log_output += "$s" + if (ui) { + var lines = 50 + log_output_ui = (log_output_ui + "$s").takeLastWhile { + if (it == '\n') { + lines-- + } + lines > 0 + } + onLog(log_output_ui) + } + } + + + + @Serializable data class Vector(val x: Float, val y: Float) { fun absSquared(): Float { return this.x * this.x + this.y * this.y } + fun plus(vector: Vector): Vector { return Vector(this.x + vector.x, this.y + vector.y) } + fun max(other: Vector): Vector { return Vector(max(this.x, other.x), max(this.y, other.y)) } + fun min(other: Vector): Vector { return Vector(min(this.x, other.x), min(this.y, other.y)) } + operator fun minus(vector: Vector): Vector { return Vector(this.x - vector.x, this.y - vector.y) } } + @Serializable class PointerPath( val number: Int, val start: Vector, @@ -57,15 +88,18 @@ class TouchGestureDetector( fun sizeSquared(): Float { return (max - min).absSquared() } + fun getDirection(): Vector { return last - start } + fun update(vector: Vector) { min = min.min(vector) max = max.max(vector) last = vector } } + private fun PointerPath.isTap(): Boolean { return sizeSquared() < TOUCH_SLOP_SQUARE } @@ -80,6 +114,14 @@ class TouchGestureDetector( LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() TAP_TIMEOUT = ViewConfiguration.getTapTimeout() DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + + log( + "TOUCH_SLOP: $TOUCH_SLOP\n" + + "DOUBLE_TAP_SLOP: $DOUBLE_TAP_SLOP\n" + + "LONG_PRESS_TIMEOUT: $LONG_PRESS_TIMEOUT\n" + + "TAP_TIMEOUT: $TAP_TIMEOUT\n" + + "DOUBLE_TAP_TIMEOUT: $DOUBLE_TAP_TIMEOUT\n======================\n" + ) } private var paths = HashMap() @@ -92,12 +134,13 @@ class TouchGestureDetector( (0.., timeStart: Long, timeEnd: Long) { + log(" - gesture complete") + log("\npaths = ${paths.entries.map { (x,y) -> "$x: ${Json.encodeToString(y)}"}}", false) val duration = timeEnd - timeStart val pointerCount = paths.entries.size + log("\nDuration: $duration, pointers: $pointerCount") if (paths.entries.isEmpty()) { return } @@ -155,41 +201,63 @@ class TouchGestureDetector( // Ignore swipes at the very top, since this interferes with the status bar. // TODO: replace 100px by sensible dp value (e.g. twice the height of the status bar) if (paths.entries.any { it.value.start.y < 100 }) { + log("\nToo close to the top") return } if (pointerCount == 1 && mainPointerPath.isTap()) { // detect taps + log("\nSlop: ${sqrt(mainPointerPath.sizeSquared())} - click") + log("\nTime since last click: ${timeStart - lastTappedTime}") + log( + "\nDistance to last click: ${ + sqrt( + (mainPointerPath.last - (lastTappedLocation ?: Vector( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY + ))).absSquared() + .toDouble() + ) + }" + ) if (duration in 0..TAP_TIMEOUT) { if (timeStart - lastTappedTime < DOUBLE_TAP_TIMEOUT && lastTappedLocation?.let { - (mainPointerPath.last - it).absSquared() < DOUBLE_TAP_SLOP_SQUARE} == true + (mainPointerPath.last - it).absSquared() < DOUBLE_TAP_SLOP_SQUARE + } == true ) { + log("\nDouble click detected") Gesture.DOUBLE_CLICK.invoke(context) } else { + log("\nNot a double click: last tap too long ago") lastTappedTime = timeEnd lastTappedLocation = mainPointerPath.last } } else if (duration > LONG_PRESS_TIMEOUT) { // TODO: Don't wait until the finger is lifted. // Instead set a timer to start long click as soon as LONG_PRESS_TIMEOUT is reached + log("\nLong click detected") Gesture.LONG_CLICK.invoke(context) } } else { + log("\nSlop: ${sqrt(mainPointerPath.sizeSquared())} - not a click") // detect swipes val doubleActions = LauncherPreferences.enabled_gestures().doubleSwipe() val edgeActions = LauncherPreferences.enabled_gestures().edgeSwipe() var gesture = getGestureForDirection(mainPointerPath.getDirection()) + log("\nbase gesture: ${gesture}") if (doubleActions && pointerCount > 1) { if (paths.entries.any { getGestureForDirection(it.value.getDirection()) != gesture }) { // the directions of the pointers don't match + log("\ndirections of the pointers don't match") return } gesture = gesture?.let(Gesture::getDoubleVariant) + log(", double variant: ${gesture}") } // detect triangles @@ -197,35 +265,40 @@ class TouchGestureDetector( val startEndMax = mainPointerPath.start.max(mainPointerPath.last) when (gesture) { Gesture.SWIPE_DOWN -> { - if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { + if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { gesture = Gesture.SWIPE_LARGER } else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) { gesture = Gesture.SWIPE_SMALLER } } + Gesture.SWIPE_UP -> { - if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { + if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) { gesture = Gesture.SWIPE_LARGER_REVERSE } else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) { gesture = Gesture.SWIPE_SMALLER_REVERSE } } + Gesture.SWIPE_RIGHT -> { - if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { + if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { gesture = Gesture.SWIPE_V } else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) { gesture = Gesture.SWIPE_LAMBDA } } + Gesture.SWIPE_LEFT -> { - if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { + if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) { gesture = Gesture.SWIPE_V_REVERSE } else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) { gesture = Gesture.SWIPE_LAMBDA_REVERSE } } - else -> { } + + else -> {} } + log(", v-variant: $gesture") if (edgeActions) { if (mainPointerPath.max.x < edgeWidth * width) { @@ -240,10 +313,12 @@ class TouchGestureDetector( gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM) } } + log(", edge-variant: $gesture") if (timeStart - lastTappedTime < 2 * DOUBLE_TAP_TIMEOUT) { gesture = gesture?.getTapComboVariant() } + log(", tap-combo-variant: $gesture") gesture?.invoke(context) } } diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/home.xml index ecefdea..4c8a262 100644 --- a/app/src/main/res/layout/home.xml +++ b/app/src/main/res/layout/home.xml @@ -48,4 +48,22 @@ app:layout_constraintEnd_toEndOf="parent" app:srcCompat="@drawable/baseline_settings_24"/> + + +