When woken, 'onUpdate' is called 6 times before `onExitSleep` called

Hello friends,

Does anyone else have a watch face that makes it obvious when a low-power mode paint has happened?  It seems that onUpdate() is getting called more often that it should during low-power mode during the wake-up sequence, and I want to know if this is specific to my faces or general across Connect IQ. 

If a watch face wouldn't change what's displayed between these paints, it would be hard to notice this.  But my faces have some features that can make this more visible (described below).

I started looking into this because a few of my customers have been complaining about a lagginess when the watch face swaps from low-power mode to high-power mode.  I can reproduce this on my devices (including an Epix Gen 2 Pro 51mm), but this behavior isn't replicated in the simulator on any device.

On various AMOLED devices, when I wake the screen via a tap or the light button, onUpdate() gets called ~6 times (in immediate succession) before onExitSleep() is called.  Because of this, the watch face is getting painted 6 extra times in the "low power mode" display before the "high power mode" display is painted.  As a result, the watch appears to lag when switching from low power mode to high power mode, because it continues for a second or so in the low-power mode before finally switching.

I would expect that upon a wake event (such as a screen tap or pressing the "light" button), the next call would be onExitSleep() and then onUpdate().  We would generally want the watch to immediately wake up and show the high-power mode. 

To be clear:

  • I do not use animations or anything that would be expected to trigger this behavior (nor should they work in low-power mode)
  • There is no code that's running WatchUi.requestUpdate();

In general, one major limitation Garmin enforces is that in low-power mode developers cannot use timers or otherwise refresh the screen more than once per minute.  So this suggests that it's unlikely I've written code that is causing this.

I have reviewed other posts in the forums and not found anything related.  This was the closest post I found:

onUpdate is called twice
This post doesn't seem to involve the sleep/wake events.

The docs aren't exactly precise about what to expect, but they do say this:

When a gesture occurs while running in low power mode the system will call onExitSleep() to notify the application that the transition to high power mode has occurred.

The only thing I can see in the docs that may suggest what I'm seeing is somehow expected is this bullet under the onUpdate() method:

More than one call to onUpdate() may occur during View transitions

But I understand this to apply more to if a view gets pushed with an animation like SLIDE_X.  No such thing is happening here that would suggest this is a view transition.

I have thought about building some kind of workaround to prevent rapid low-power mode repaints, but before I do that, I wanted to ask the community if this could be reproduced by others before reporting it as a formal bug.

This is particularly obvious on my faces, because I've built my faces with an internal burn-in protection system that moves each low-power mode paint up to 6px from the center.  In addition, my watch faces include an advanced option to paint the draw time in milliseconds for each paint.  As a result, you can clearly see the individual paints happening in low-power mode before the wake, since the red numbers update rapidly and the whole face moves with each paint.

Here's a video showing this behavior.  Watch the red numbers on the left.  Every time that updates corresponds to a new call to onUpdate().  

Click here to play this video

If you can't view the directly-embedded video above, try this link: www.dropbox.com/.../IMG_3610-copys.mov

It is fully possible that this is happening to other developers but not getting noticed, since you'd have to be able to see some variation per-paint.  Otherwise, it'll just be repainting the same thing, which wouldn't be noticeable.  But it would be causing lagginess and wasting some battery.

Some other details:

  • The watch face in this video is: LUXE Relux Daytona (RLX)
  • My device is running 15.77 (I'm part of the beta program, but this doesn't appear to be a beta issue)

I've also noticed a hard-to-replicate issue where the low-power mode actually gets stuck triggering updates once per second.  This is especially obvious on my faces, because each paint in low-power mode moves the entire face slightly to reduce the chances of burn-in.  I'll make a separate post about that, since it seems to be a separate issue.

Top Replies

All Replies

  • the code that actually runs in onUpdate() doesn’t matter, it only matters what pixels the firmware decides to refresh

    Ha! That's nonsensical. The CPU time during the onUpdate() absolutely does have a battery impact.

    in case you detect this beheaviour then use the buffered bitmap from the previous call to onUpdate to draw to the screen instead of redrawing it

    Yeah, all my faces make use of numerous Buffered Bitmaps in layers to accelerate/cache stuff.  That's a core part of what I do.

    But my logic assumes that a full paint of the "per-minute" Buffered Bitmap is needed if:

    • The device is in Low Power mode (since this should be called once per minute, and things need to be painted)
    • Or the power mode just changed (and I need to swap the BB from one mode to the other)

    By the documentation, in low power mode, onUpdate() should only happen on the top of the minute, once per minute.  But a firmware bug has been calling onUpdate() in Low Power mode once every second.  It's been causing my faces to run through the "it's a new minute" logic and repaint the stuff that changes from minute-to-minute (including the time hands and the gauges).

    My most recent system just entirely skipped painting when I detected neither the clock time nor the power mode had changed since the last paint.  That seemed the most expedient.  But as we've established, some devices clear the screen each time onUpdate is called, while some others don't.

    I think I'm going to develop a new system that just throws up the prior Buffered Bitmaps if this is detected.  The cost of painting a Buffered Bitmap is still non-zero.  It's not free, by the way.  So the firmware bugs are still going to cause some drain.  

  • Unfortunately the latest beta hasn’t fixed either bug.  

  • I think drawing a full screen bitmap for excessive/incorrect calls to onUpdate is the best workaround since it’s compatible with all devices Thumbsup

  • The CPU time during the onUpdate() absolutely does have a battery impact.

    Wow you mean Garmin devices actually obey the laws of physics!!!!1!

    The point that, for watchfaces, onPartialUpdate() allows (and requires) a clipping region to be set, so that only part of the screen can be truly updated, as opposed to onUpdate(), which does not, would've been well taken. But to go further and imply that the actual code in onUpdate() which figures out what to draw and makes the dc.draw calls is essentially free (or constant time/energy) is kind of ridiculous to me.

    I think I'm going to develop a new system that just throws up the prior Buffered Bitmaps if this is detected.  The cost of painting a Buffered Bitmap is still non-zero.  It's not free, by the way. 

    Yeah I think this is one of the most popular workarounds / optimizations for onUpdate() issues (whether it's the bug that you mentioned, or the indisputable fact that, in general, you need to redraw the whole screen in onUpdate()). I can't imagine it would be free though.

    But as we've established, some devices clear the screen each time onUpdate is called, while some others don't.

    But as jim_m_58 correctly pointed out, a notification can be displayed on top of your app (whether it's a watchface, device app or data field), so I don't think you can write your apps to behave differently based on the (hardcoded) knowledge that a device clears the screen before onUpdate() or not.

    I think what would really need to happen is for the CIQ API to let devs know whether it's safe to only draw part of the screen in onUpdate(), as in a recent feature request. (It would be safe if nothing else had been drawn on top of your app, and if the screen hasn't been cleared.)

    I doubt this will ever happen.