Vivoactive 3: KEY_ENTER not detected?

EDIT: I guess this is not a bug. I guess VA3 widgets just can't use the physical KEY.

My mistake.

---

Connect IQ SDK 3.6

Some users my Stop and Go stopwatch widget are reporting that pressing the KEY does not work. Instead of starting the stopwatch, it only returns them to the watchface.
https://apps.garmin.com/en-US/apps/3...0-a7dc7bdc72b8

The way it's supposed to work is:
- If you press the key on the widget "home page":
-- If the timer is reset, the widget is entered and the stopwatch starts
-- If the timer is running/stopped, the widget is entered

- If you press the key when already in the widget, the stopwatch state is toggled between started/stopped.

This works fine for me in the simulator and on a real 935. Two VA3 users have complained about being unable to start the stopwatch. Another VA3 user says that it works just fine.

Couple of wrinkles:
- I have code to detect long presses. Long press of start does something on platforms where this is not taken up by some other function. I realize that onKeyPressed/onKeyReleased are not supported on every platform, but I have fallback code in onKey() which checks to see if onKeyPressed was previously called for that key. I guess the only way this could fail is if onKeyPressed is called, but onKeyReleased never is, in which case I would have to skip that logic for VA3.

- I have already implemented a workaround based on a VA3 issue that was reported last year:

private function getKey(keyEvent)
{
var k = keyEvent.getKey();
if (sVivoactive3)
{
if (k == 45 || k == 47 || k == 49)
{
k = WatchUi.KEY_ENTER;
}
}
return k;
}


Reading through the forums, I see these related issues:
https://forums.garmin.com/forum/deve...ansferred-code
https://forums.garmin.com/forum/deve...-on-the-device
  • Here's the gist of the code. (I know it isn't great).

    I know there's no portable way to do what I'm trying to do, so maybe the problem is somewhere here....

    private var keyPressedStartTime = null;
    private var pressedKey = null;

    private const longPressThreshold = 1000;

    function handleKeyPress(k)
    {
    //... handle key press
    // ...

    return handled;
    }
    function handleLongPress(k)
    {
    // handle key long press
    // ...
    if (handled)
    {
    handledKey = k;
    }
    return handled;
    }

    function isLongPressSupported(k)
    {
    // return true if we want to detect long press for this key, false otherwise
    // ...
    }



    function detectLongPress(k) {
    pressedKey = k;
    keyPressedStartTime = System.getTimer();

    longPressTimer.stop();
    longPressTimer.start(method(:longPressTimerExpired), longPressThreshold, false);
    }

    function isDetectingLongPress(k) {
    if (handledKey == k)
    {
    handledKey = null;
    return true;
    }


    return (pressedKey == k && keyPressedStartTime != null);
    }

    function checkLongPress(k) {
    longPressTimer.stop();
    keyPressedStartTime = null;

    var now = System.getTimer();
    if (pressedKey != k || keyPressedStartTime == null || now - keyPressedStartTime < longPressThreshold)
    {
    pressedKey = null;
    keyPressedStartTime = null;
    return false;
    }
    pressedKey = null;
    keyPressedStartTime = null;
    return true;
    }

    function longPressTimerExpired() {
    if (keyPressedStartTime)
    {
    keyPressedStartTime = null;
    onKeyLongPress(pressedKey);
    }
    }

    function onKeyLongPress(k) {
    return handleLongPress(k);
    }

    function onKeyPressed(keyEvent) {
    var k = getKey(keyEvent);
    var t = keyEvent.getType();
    System.println("onKeyPressed(): keypressed = " + k + ", type = " + t);

    if (isLongPressSupported(k))
    {
    detectLongPress(k);
    return true;
    }
    return false;
    }


    function onKeyReleased(keyEvent) {
    var k = getKey(keyEvent);
    var t = keyEvent.getType();

    if (pressedKey != k)
    {
    return false;
    }

    if (isLongPressSupported(k))
    {
    if (checkLongPress(k))
    {
    return onKeyLongPress(k);
    }
    else
    {
    // can only call this after calling checkLongPress(),
    // horrible design and poorly named functions
    return onKey(new WatchUi.KeyEvent(k, WatchUi.PRESS_TYPE_ACTION));

    // would've made more sense to call handleKeyPress(k) here
    // but that's not what I did
    }
    }
    return false;
    }


    function onKey(keyEvent) {
    var k = getKey(keyEvent);
    var t = keyEvent.getType();
    System.println("onKey(): key = " + k + ", type = " + t);
    if (isLongPressSupported(k))
    {
    if (isDetectingLongPress(k))
    {
    return true;
    }


    }
    return handleKeyPress(k);
    }

  • Input delegates are a bit different on the va3 than other devices. Rule of thumb, always return true (except for onBack, then it's up to your app).

    See this post in one of the threads you mention.

    https://forums.garmin.com/forum/developers/connect-iq/1295310-problems-on-va3-with-transferred-code?p=1296342#post1296342

    Part of it is the single button, and for things like where you are trying to detect a long press, keep in mind that on the device, long press of the button takes you to the controls menu.
  • jim_m_58 thanks, you're a lifesaver!

    Just don't understand why some people are seeing problems and others aren't. Probably ROW versus APAC or different firmware versions.

    I also wish the simulator would emulate this particular behaviour. And I wish the simulator would emulate the "hold for menu" behaviour for the various devices, especially VA3 and VAHR.

    So is this something that should be done going forward. Or even "retroactively" for all previous devices?

    EDIT: I re-read your linked post and it seems like the answer to the question is "yes". (As in, always return true).

    EDIT: Now I found the definitive source of the bug. I had code in onKeyPressed that purposely returned false for VA3 in an attempt to avoid trying to detect a long press. (Because of my fallthrough code). I should've returned true....

  • Using onKeyPressed and onKeyReleased can be very tricky with different devices. (I never use either), as based on device, some have short/long presses for native functions (long press up for menu, long press of the button for controls on the va3, long press enter for menu on edge devices, etc. And I think it's even a bit trickier with thing like the fr 23x.

    On the vahr in the sim, it does seem something is broken, and any press of the right button is seen as onMenu (3.0.7) (long press of the right button is menu on a vahr, not a long hold of the screen like a va3). The va3 seems fine to me in the sim for this.

    Update: I think I was running an older version of the sim, as with 3.0.7 the right button on the vahr it's back working as it should.
  • jim_m_58 Yes, I reported that VAHR sim bug and I think they may have fixed it already.

    What I meant is that I wish the fact that native controls menus are activated by certain long presses (e.g. hold Back on VAHR, hold KEY on VA3) was emulated in the simulator, because no app can ever do anything with those long presses, not even open their own menu.

    Other things, like when a hotkey is set by default (hold DOWN) are a little more ambiguous. I guess you just have to read the manual or avoid trying to detect long presses. I think someone implemented a stopwatch where you take laps by double-clicking....

    Anyway, I just wanted to make stopwatch app like the 935 native stopwatch, except where you don't have to navigate a menu and press 4 buttons to just to restart the timer. So I thought I'd be "clever" and use long presses for some things. (Like close widget versus reset stopwatch, or discard recording versus save recording).

    It turns out that only works well for five button watches. But I still managed to make the menuless stopwatch with activity recording that I wanted to make. Touchscreen users have to do some swiping, though.

    Thanks again for your help! I was starting to beg the VA users to put a debug PRG on their watch, and we all know how annoying that is. (It's annoying enough for devs, I can't imagine how much users hate it).

  • Remember, this is a simulator and not an emulator! There are a number of things you don't see in the sim, like native actions for short/long presses, and even the timeout for widget.

    Stick with the UX Guide and don't even try to detect your own long presses is my advice. There are simply too many different devices that do things differently (single button, those with music, hotkeys, etc)

    You also want to stay close to how users interact with other apps on that device.
  • jim_m_58, believe it or not, I did try to stick with native behaviour as closely as possible, wherever the native functionality didn't conflict with the stopwatch functionality (e.g. taking laps).

    I also wanted to stick with the traditional stopwatch controls where pressing Lap while the timer is stopped resets the stopwatch. Even the stopwatch on an iPhone works like this. I also wanted to give 5-button watches users the ability to hold START to reset the stopwatch, in case they wanted to disable the Lap reset functionality, to avoid accidental resets.

    TL;DR, I put a lot of thought into this stuff, even if I stubbornly decided to overlay my own "alternate controls" (e.g. hold back) on top of the native controls....

    ---

    Every device that has a physical back button uses it to take laps, except 630 which uses Lap to take laps and Back to close the app/widget. VA3 native activities double-tap to take a lap, so I emulated that, too. (I've seen stopwatch apps where you press Down to take a lap. Maybe because it was easier to port to VA3?)

    On VA3 only, swipe right closes the Widget.

    I used a third party menu for CIQ1 which didn't have native touchscreen support, so I added it. Just like it works on 630 and all the other old touchscreen devices that don't let you swipe through menus but force you to tap.

    Of course I had to change my up/down swipes -- for scrolling through laps -- to left/right for VA.

    Having said all of that, I also wanted alternate actions, like:
    - Hold Back to close at any time (instead of taking a lap)
    - Discard instead of Save activtiy

    So I mapped those to different swipes, double-taps, or Back long presses, all with on-screen prompts. Is that really worse than navigating a menu (935), or pressing some random button like Up or Down, or not being able to close the app without resetting the stopwatch?

    I guess I wanted to have my cake and eat it too. I just don't like the idea of having a menu in a stopwatch, because it's too slow to reset/restart the stopwatch, IMO. I wanted to make a modern stopwatch that also has traditional quick controls so you don't have to mess around with menus. While also giving the user the ability to close the app while the stopwatch is running/paused. Others have accomplished this simply by not letting you take laps, so the Back button is free.

    Having said all of that, it was a nightmare for coding and testing....

    I'll also say that every single stopwatch in the store has slightly different controls. I feel like I at least have prompts for every single thing that isn't obvious (i.e. press START to start the timer.)
  • BTW, edge devices also have a seperate lap button, and start and enter are separate buttons. And no buttons on a oregon or rino, and tons on the GPSMAP66.. The fun of supporting multiple devices in apps and widgets!

    The input sample in the SDK comes in handy.
  • Yeah, thank god I only supported watches.... I should've just gone with "press down for lap".

    It was a nightmare, but I made an app and widget that at least I would use....

    Yeah, I should've looked at that sample first <_<. But I knew most of the differences having owned a 630 and 935, and having followed Garmin watches for a couple of years.
  • Now I'm returning true for all 3 of onKey(), onKeyPressed() and onKeyReturned() (for VA3), and handling KEY_ENTER in onKey() by starting the stopwatch.

    A VA3 user is still reporting that it doesn't work.... Which shouldn't surprise me because the "return false" bug was actually a fairly recent change I made after people complained.

    I'll post more if I can get the user to sideload a debug PRG.

    I wonder if just the mere presence of onKeyPressed() and onKeyReturned() functions in my subclass is causing a problem....