mirror of
https://github.com/jrpie/Launcher.git
synced 2025-02-22 22:11:27 +01:00
add logging to collect debugging data
This commit is contained in:
parent
86528f4e27
commit
51ed03dd72
5 changed files with 119 additions and 16 deletions
|
@ -1,3 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name" translatable="false">μLauncher [debug]</string>
|
||||
<string name="app_name" translatable="false">μLauncher [gesture detection debug]</string>
|
||||
</resources>
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Int, PointerPath>()
|
||||
|
@ -92,6 +134,7 @@ class TouchGestureDetector(
|
|||
(0..<event.pointerCount).associateBy { event.getPointerId(it) }
|
||||
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
log("\n========================\nNew gesture")
|
||||
paths = HashMap()
|
||||
}
|
||||
|
||||
|
@ -144,8 +187,11 @@ class TouchGestureDetector(
|
|||
}
|
||||
|
||||
private fun classifyPaths(paths: Map<Int, PointerPath>, 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
|
||||
|
@ -203,6 +271,7 @@ class TouchGestureDetector(
|
|||
gesture = Gesture.SWIPE_SMALLER
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_UP -> {
|
||||
if (startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
|
||||
gesture = Gesture.SWIPE_LARGER_REVERSE
|
||||
|
@ -210,6 +279,7 @@ class TouchGestureDetector(
|
|||
gesture = Gesture.SWIPE_SMALLER_REVERSE
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_RIGHT -> {
|
||||
if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
gesture = Gesture.SWIPE_V
|
||||
|
@ -217,6 +287,7 @@ class TouchGestureDetector(
|
|||
gesture = Gesture.SWIPE_LAMBDA
|
||||
}
|
||||
}
|
||||
|
||||
Gesture.SWIPE_LEFT -> {
|
||||
if (startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
|
||||
gesture = Gesture.SWIPE_V_REVERSE
|
||||
|
@ -224,8 +295,10 @@ class TouchGestureDetector(
|
|||
gesture = Gesture.SWIPE_LAMBDA_REVERSE
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,4 +48,22 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/baseline_settings_24"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_output"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="start"
|
||||
android:gravity="bottom"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="Log\nLog\nLog" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_copy_log"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Copy log to clipboard"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">μLauncher</string>
|
||||
<string name="app_name" translatable="false">μLauncher [gesture detection]</string>
|
||||
<!--
|
||||
-
|
||||
- Settings
|
||||
|
|
Loading…
Add table
Reference in a new issue