commit a45553746c352033b7f7c80e9134266b7f513d33 truex <truex@equwal.com> 2026-09-21 03:03:52 -0700 Fix a Camera key in the drawing of a device that has none The Viwoods reader declares a camera key to Android and has no such button. Device.keys added the keys that detection found, so the drawing showed a Camera key. A declared key is not a button. Device.keys now lists the keys of the device profile and the keys that were really pressed in normal use. The Detect screen still lists what the device declares, and marks a key that was never seen. DeviceKeysTest holds the input that caused the defect. Version 0.0.11-alpha, version code 11.
CHANGELOG.md | 13 ++++++++ .../java/dev/equwal/assistkey/device/Device.kt | 25 +++++++------- .../java/dev/equwal/assistkey/ui/DetectActivity.kt | 11 ++++-- .../main/java/dev/equwal/assistkey/ui/Summary.kt | 4 +-- .../dev/equwal/assistkey/device/DeviceKeysTest.kt | 39 ++++++++++++++++++++++ .../java/dev/equwal/assistkey/ui/SummaryTest.kt | 6 ++-- gradle.properties | 4 +-- 7 files changed, 81 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cbf437..890f6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.0.11-alpha - 2026-09-21 + +Version code 11. + +- **Fix: a Camera key in the drawing of a device that has none.** The Viwoods + reader declares a camera key to Android and has no such button. Detection + put the declared key in the drawing. The drawing now shows only the buttons + that the device profile knows, and a button that was really pressed in + normal use. The Detect screen still lists what the device declares, and + marks a key that was never seen. Regression test: `DeviceKeysTest`. + The double tap of Power uses the camera *intent* of Android. It needs no + camera button. + ## 0.0.10-alpha - 2026-09-21 Version code 10. diff --git a/app/src/main/java/dev/equwal/assistkey/device/Device.kt b/app/src/main/java/dev/equwal/assistkey/device/Device.kt index d0649a6..561c2f8 100644 --- a/app/src/main/java/dev/equwal/assistkey/device/Device.kt +++ b/app/src/main/java/dev/equwal/assistkey/device/Device.kt @@ -74,20 +74,21 @@ object Device { else listOf(HwKey.VOL_UP, HwKey.VOL_DOWN) /** - * The remappable keys to show: what the profile knows about, what detection - * found, plus any other supported key this device has actually produced. + * The remappable keys to show: what the profile knows about, plus any other + * supported key this device has actually produced in normal use. * - * Detection only adds. A profile key stays on the list even if no source - * reports it, because a firmware hook can hide a key that is really there. + * What [Detect] finds is NOT on this list. A device declares keys it does + * not have: the Viwoods reader declares a camera key, and has none. Only a + * real press proves a real button. */ - fun keys(c: Context): List<HwKey> { - val seen = c.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - .getStringSet(K_SEEN, emptySet()).orEmpty() - val detected = Detect.stored(c)?.keys.orEmpty().filter { it.interceptable } - // The profile knows where its buttons sit. The other keys follow in a fixed order. - val others = (detected + HwKey.interceptable.filter { it.token in seen }).sortedBy { it.ordinal } - return (builtIn + others).distinct() - } + fun keys(c: Context): List<HwKey> = listed( + builtIn, + c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getStringSet(K_SEEN, emptySet()).orEmpty() + ) + + /** The profile knows where its buttons sit. The keys that were seen follow in a fixed order. */ + fun listed(profileKeys: List<HwKey>, seenTokens: Set<String>): List<HwKey> = + (profileKeys + HwKey.interceptable.filter { it.token in seenTokens }).distinct() private const val K_UNKNOWN = "unknown_keys" diff --git a/app/src/main/java/dev/equwal/assistkey/ui/DetectActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/DetectActivity.kt index b824770..4f883b6 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/DetectActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/DetectActivity.kt @@ -4,6 +4,8 @@ import android.app.Activity import android.os.Build import android.widget.LinearLayout import dev.equwal.assistkey.device.Detect +import dev.equwal.assistkey.model.HwKey +import dev.equwal.assistkey.device.Device import dev.equwal.assistkey.shell.Shell import dev.equwal.assistkey.ui.Ui.header import dev.equwal.assistkey.ui.Ui.note @@ -66,13 +68,18 @@ class DetectActivity : Activity() { } private fun buttons(col: LinearLayout, found: Detect.Result) { - col.header("Buttons found") + col.header("Keys the device declares") if (found.keys.isEmpty()) { col.note("None found.") return } found.keys.forEach { key -> - col.row(key.label, "Code " + key.code + (if (key.interceptable) "" else " · kept by the system")) + val real = key == HwKey.POWER || key in Device.keys(this) + col.row( + key.label, + "Code " + key.code + (if (key.interceptable) "" else " · kept by the system"), + state = if (real) null else "No such button seen" + ) } } diff --git a/app/src/main/java/dev/equwal/assistkey/ui/Summary.kt b/app/src/main/java/dev/equwal/assistkey/ui/Summary.kt index 8a96919..a4c6c95 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/Summary.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/Summary.kt @@ -24,8 +24,8 @@ object Summary { /** The detection row: how many buttons the last detection found. */ fun detected(keyCount: Int?): String = when { keyCount == null -> "Not run yet" - keyCount == 0 -> "No named buttons found" - else -> count(keyCount, "button") + " found" + keyCount == 0 -> "The device declares no named keys" + else -> count(keyCount, "key") + " declared by the device" } /** The Power button, which has its own screen. */ diff --git a/app/src/test/java/dev/equwal/assistkey/device/DeviceKeysTest.kt b/app/src/test/java/dev/equwal/assistkey/device/DeviceKeysTest.kt new file mode 100644 index 0000000..5cd0fe8 --- /dev/null +++ b/app/src/test/java/dev/equwal/assistkey/device/DeviceKeysTest.kt @@ -0,0 +1,39 @@ +package dev.equwal.assistkey.device + +import dev.equwal.assistkey.model.HwKey +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.util.Random + +class DeviceKeysTest { + + private val viwoods = listOf(HwKey.VOL_UP, HwKey.VOL_DOWN, HwKey.AI) + + /** + * Regression, 0.0.10: the Viwoods reader declares a camera key and has + * none. Detection stored ["ai","vol_up","vol_down","camera","power"], and + * the drawing showed a Camera key. A declared key is not a button. + */ + @Test fun `a key that the device only declares is not in the drawing`() { + val shown = Device.listed(viwoods, seenTokens = emptySet()) + assertEquals(viwoods, shown) + assertFalse(HwKey.CAMERA in shown) + } + + @Test fun `a key that was really pressed is in the drawing, after the profile keys`() { + assertEquals(viwoods + HwKey.PAGE_DOWN, Device.listed(viwoods, setOf("page_down"))) + } + + @Test fun `the drawing has the profile keys first, no key twice, and nothing that was not seen`() { + val r = Random(5) + repeat(300) { + val seen = HwKey.entries.filter { r.nextInt(3) == 0 }.map { it.token }.toSet() + "no_such_key" + val shown = Device.listed(viwoods, seen) + assertEquals(viwoods, shown.take(viwoods.size)) + assertEquals(shown.distinct(), shown) + assertTrue(shown.all { it in viwoods || (it.token in seen && it.interceptable) }) + } + } +} diff --git a/app/src/test/java/dev/equwal/assistkey/ui/SummaryTest.kt b/app/src/test/java/dev/equwal/assistkey/ui/SummaryTest.kt index bd89474..ad18b13 100644 --- a/app/src/test/java/dev/equwal/assistkey/ui/SummaryTest.kt +++ b/app/src/test/java/dev/equwal/assistkey/ui/SummaryTest.kt @@ -24,9 +24,9 @@ class SummaryTest { @Test fun `detected says whether it has run, then what it found`() { assertEquals("Not run yet", Summary.detected(null)) - assertEquals("No named buttons found", Summary.detected(0)) - assertEquals("1 button found", Summary.detected(1)) - assertEquals("4 buttons found", Summary.detected(4)) + assertEquals("The device declares no named keys", Summary.detected(0)) + assertEquals("1 key declared by the device", Summary.detected(1)) + assertEquals("4 keys declared by the device", Summary.detected(4)) } @Test diff --git a/gradle.properties b/gradle.properties index 1af4b2a..dd01f89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,8 @@ android.nonTransitiveRClass=true kotlin.code.style=official # Release identity. versionCode must rise with every upload to Play. -assistkey.versionName=0.0.10-alpha -assistkey.versionCode=10 +assistkey.versionName=0.0.11-alpha +assistkey.versionCode=11 # The free beta ends on this date (UTC) at the latest. It is the backstop for # installs that cannot reach Google Play; the real switch is the