Android SDK: Starting companion app from watch stops working with OREO

Current hack that replaces a missing functionality in the ConnectIQ Android SDK will stop working when the companion apps Android API Level 26 (Oreo 8.0).

I'm talking about the hack that enables you to start the Android app by a message from the watch app. You do this by capturing a Broadcast Intent
"com.garmin.android.connectiq.INCOMING_MESSAGE" (as demonstrated here) and then checking for the correct AppId in its extras.

This is no more possible in Android API Level 26 and higher.

According to https://developer.android.com/about/...tml#broadcasts, apps will not be able to register for implicit intents in manifest anymore and would need a running service to react to them.

PLEASE: Add this feature to the ConnectIQ SDK. Allow us to start the companion app from the watch.
  • Disclaimer: I'm not an Android developer (nor Garmin developer), just working on a pet project and I need something like this. I've just found this thread after trying to look for a broadcast or something that could help to re-launch my companion app (I mean, the foreground service), after it being killed by the system in <30 min running in the background, no matter what I tried.

    At very quick glance, creating a broadcaster for com.garmin.android.connectiq.INCOMING_MESSAGE indeed makes my companion app *launched* (for the broadcaster) on sending a message from the watch. That is on a phone running Android 13. I observed some hiccups at certain moment, but it looks really promising - I'm investigating it further. I keep my fingers crossed/I am happy and grateful to the people sharing the information above.

    P.S. What I observed so far is that in this scenario the message itself is not delivered to the companion via Connect SDK, apparently because SDK is not yet initialized at the moment the message is delivered to the phone/it takes some time to initialize it. That does not look like a big problem (compared to the inability to launch the companion app at all): I mean, I can work around that, e.g. by sending a dummy message to launch the app, and sending the "right" message after that, or I can wait for ACK message back from the companion, to ensure that it's indeed running and etc.

  • Thank you for sharing, that sounds promising!

    Given that what you said already works for you, I have another idea what may make it easier for you: You could also blindly send a "ping" in regular intervals to the Android phone. That may keep the app alive throughout the whole time the app on the Watch is running?

  • Well, tbh, that would worsen the battery life, I guess, for both the watch and the phone?

    A bit of update on the above: for some reason, I'm observing hiccups again (after letting it be killed by the system)... I mean, it no longer launches the app. When I faced something like this first time, it eventually started working again some time later. Just in case, I'm pretty sure that, when it works, the app/the service is indeed launched (not resumed or whatever) - I kill it manually when I test it/observe the process launched/ended and etc. Investigating it further...

  • OK, got it working again. Scratching my head...

  • Well, tbh, that would worsen the battery life, I guess, for both the watch and the phone?

    On the phone only if you're doing significant work in the app on the phone. I plan to just have it "idle" for the time not used.

    On the watch only if sending the ping costs battery but I don't think it will "weigh" a lot, especially since you won't have the app opened 24/7 I assume.

    But of course this is only if you need the watch-phone communication at all times; for my use case I need the phone app to respond as soon as possible

  • OK, just to share a bit of findings:

    1. I got my app killed in background was because I had a custom app killer prebundled with my phone model, that I had to disable (https://dontkillmyapp.com/ was to rescue).

    2. The observations that I had when testing the launching via the INCOMING_MESSAGE (when the app did not get relaunched at certain times) are probably related to the fact that I was *crashing* it as part of the testing, so it ended up with the system rejecting to launch it because it considered the process as "bad" (i.e. "crashing a lot"), you can see the example below (alongside with rejecting to relaunch for another intent coming from a scheduled alarm):

    13:23:10.438 BroadcastQueue W Unable to launch app com.garmin.android.apps.connectiq.sample.comm/10295 for broadcast Intent { act=com.garmin.android.connectiq.INCOMING_MESSAGE flg=0x10 pkg=com.garmin.android.apps.connectiq.sample.comm (has extras) }: process is bad
    13:24:58.688 BroadcastQueue W Unable to launch app com.garmin.android.apps.connectiq.sample.comm/10295 for broadcast Intent { flg=0x10 cmp=com.garmin.android.apps.connectiq.sample.comm/.broadcastreceivers.KeepAwakeReceiver }: process is bad
  • OK, I believe that I figured it out. The intent that is sent with INCOMING_MESSAGE, at least in my case, is not an implicit one: it mentions the package of my app - you can clearly see `pkg=com.garmin.android.apps.connectiq.sample.comm` above. Hence INCOMING_MESSAGE can (technically) be used for automatic launching of the app.

    I guess that SDK/Garmin Connect gets the package id when the app connects to SDK (note that it provides the watch app ID), and *stores* that package id for later. Then it uses that package id when broadcasting INCOMING_MESSAGE that originates from the watch app with that given ID. Actually, I guess that INCOMING_MESSAGE is just a part of communication channel used by SDK for talking to each companion application, hence it sends those messages using *explicit* package id, everytime: I guess that there's another receiver for those events registered by SDK itself for each companion app, e.g. on call to ConnectIQ.registerForAppEvents (in runtime).

  • Hey Grigorii, I now also had the time to test things. It says

    pkg=com.garmin.android.apps.connectiq.sample.comm

    for me as well. If I understand correctly, the whole thing only works because the Garmin Connect app publishes the intent whilst explicitly passing "our" package name, right? 

    What I have successfully tested so far:

    - restart the phone, not doing ANYTHING except for entering my pin, then locking the phone

    - wait a few minutes

    - send messages from watch to phone

    This worked as well :) Meaning that the user doesn't have to open our app first before being able to use it.

    Questions that I haven't looked into yet:

    • For how long (or for how many CPU cycle usage) does Android keep the app open 
    • How to make sure the Android app stays open as long as required
    • You mentioned blocked launches due to killing the app (either by user or by system) - did you address this with things like wake locks? You mentioned https://dontkillmyapp.com/ but did you follow user-side or developer-side instructions?

    I am using a Pixel 7 which has a perfect score on that website but I'm afraid of how things will look for users with other devices. It would suck having to buy phones from 10 different manufacturers tho but that's the beauty of Android I guess

  • If I understand correctly, the whole thing only works because the Garmin Connect app publishes the intent whilst explicitly passing "our" package name, right? 

    Yeah, you can actually see that in the implementation of SDK that is at least partially available in the source form (it's not exactly the observer registration, but it clearly passes the package name below, together with the intent action).

    • For how long (or for how many CPU cycle usage) does Android keep the app open 
    • How to make sure the Android app stays open as long as required
    • You mentioned blocked launches due to killing the app (either by user or by system) - did you address this with things like wake locks? You mentioned https://dontkillmyapp.com/ but did you follow user-side or developer-side instructions?

    I have no clue, but I got it working for "days" (I mean now I don't see it *ever* killed), without any wake locks on two of my Android phones (each model (Xiaomi and Unihertz) needed specific steps) - just following (user-side) instructions on dontkillmyapp (and getting quite a few permissions/foreground service).

    What's important in regards to operating in long term, is that Garmin Connect, particularly its service that is internally used by SDK, gets killed from time to time. Then you get ServiceUnavailableException on most SDK calls, and as a result SDK unconditionally goes into onShutdown (that's also clearly seen from the source code of SDK). What I ended up doing in that case, is *shutting the sdk* down (it's necessary to deregister some stuff done on initialize()), and initialize() it again. Still, sometimes it (Garmin Connect) gets into the state where no messages are sent to the watch, and in those cases I'm currently killing it manually (I'm using `adb shell am crash com.garmin.android.apps.connectmobile`) - that makes SDK shutdown as I just mentioned, but given that I have "restart" on the caller side, it makes the whole thing work after that again.