Skip to content

3rd party actions triggered by Di2 buttons - #345

Open
cnoons-cyq wants to merge 3 commits into
valterc:mainfrom
cnoons-cyq:external-actions
Open

cnoons-cyq wants to merge 3 commits into
valterc:mainfrom
cnoons-cyq:external-actions

Conversation

@cnoons-cyq

Copy link
Copy Markdown

Allows third-party apps to register as external action providers that can be triggered by Di2 switch events. Providers are discovered via broadcast receivers, communicate actions over broadcast intents, and appear as options in the Ki2 switch preference lists.

Includes a sample-provider module demonstrating the provider API.

Why

I'm working on an app for Cycliq that will use BLE on Karoo devices to control, monitor and report device status through a data field. I learned a lot through your Ki2 repo and thought this could be a cool feature for people using Ki2 and Cycliq devices down the track.

My exact use case will be things like using the Di2 shifter buttons to trigger light mode changes on a BLE device, or sending commands to cause the device to make footage locked so it isn't overwritten, or to take a picture!

My app is still a little while away, some more testing is required, but thought I could get the ball rolling early on this PR!

Reasons for Intent Based services

My first implementation used AIDL for IPC between Ki2 and providers. This would require providers to copy AIDL interface files and a shared ExternalAction Parcelable class into their project which I felt was a nuisance. I considered the option of a new ki2-ext-actions package to work around this, but I doubt you want to maintain a package for something you haven't implemented!

Using an Intent-based protocol eliminates the need for shared code and need for external dependencies: providers only need documented string constants and standard Android APIs (BroadcastReceiver, Intent, org.json).

During development, Ki2 calling startService() to communicate with providers was found to be blocked by Android's background service restrictions on Karoo 2 hardware. After this, the protocol was revised to use sendBroadcast() with setPackage() for all provider communication, which works reliably from any execution context.

Prior to discovering this I had been testing on a Karoo 3 but I don't have access to it right now, and I need to test further on the Karoo 3 to ensure core functionality works... Providers declare a manifest to handle broadcasts, giving them an opportunity to start their own services and execute their actions.

Protocol

  • Discovery: Providers declare a with intent-filter com.valterc.ki2.ACTION_PROVIDER. Ki2 discovers them via queryBroadcastReceivers().
  • Listing actions: Ki2 broadcasts LIST_ACTIONS to the provider's package. The provider responds with an ACTIONS_RESULT broadcast containing a JSON array of actions (id, label, icon URI, allowed switches bitmask).
  • Executing actions: Ki2 broadcasts PERFORM to the provider's package with flat extras (action ID, device ID, switch type/command/repeat, timestamp).
  • Provider-initiated refresh: When a provider's action list changes at runtime (in my case, when a BLE device is paired/unpaired), it broadcasts ACTIONS_CHANGED to Ki2 with its package name. Ki2 re-queries only that provider.

Changes

  • New classes: ExternalAction, ExternalActionDescriptor, ExternalActionTarget, ExternalActionManager, Ki2ActionEvent
  • Modified: Ki2Service (dispatches external actions on switch events), InputManager (resolves switch preferences to external action targets), SwitchListPreference (appends external actions to the preference picker using the
    standard ListPreference dialog)
  • New module: sample-provider — a minimal working example with zero Ki2 dependencies demonstrating the full protocol
  • No AIDL files: The protocol requires no shared code between Ki2 and providers

cnoons-cyq and others added 3 commits February 18, 2026 11:22
Allow third-party apps to register as external action providers that can be triggered by Di2 switch events. Providers are discovered via intent services, communicate actions over broadcast intents, and appear as options in the switch preference lists.

Includes a sample-provider module demonstrating the provider API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use queryBroadcastReceivers() instead of queryIntentServices() so the
ACTION_PROVIDER intent-filter lives on the receiver directly. This
eliminates the need for a separate stub service class in providers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, queryBroadcastReceivers() returns zero results for
external apps targeting SDK 30+, so providers are never discovered
and LIST_ACTIONS is never sent.

Verified on Karoo 2 (Android 12 / API 32) with an external provider
installed: discovery and LIST_ACTIONS round-trip both fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cnoons-cyq

Copy link
Copy Markdown
Author

Pushed ea75fc0 — small manifest follow-up.

Hit a discovery issue while testing this against an external provider on a Karoo 3 (Android 12 / API 32): queryBroadcastReceivers() returned zero results, so no LIST_ACTIONS ever went out and bound actions never appeared in the switch picker.

Root cause is Android 11+ package visibility (introduced at API 30): an app targeting SDK 30+ can only resolve receivers in apps it has explicit visibility into. Ki2 targets SDK 34 with no <queries> block, so the filter strips every external provider out of the result regardless of intent-filter match.

Fix is a four-line addition to app/src/main/AndroidManifest.xml:

<queries>
    <intent>
        <action android:name="com.valterc.ki2.ACTION_PROVIDER" />
    </intent>
</queries>

This grants Ki2 visibility into any installed app whose receiver matches the discovery action — exactly the set you want to enumerate, nothing broader.

Sample provider didn't expose this earlier because it ships in the same Gradle build and gets installed/observed via the install path; cross-app providers go through the visibility filter.

Verified end-to-end on Karoo 2 (Android 12 / API 32) after the fix:

  • queryBroadcastReceivers() returns the external provider
  • LIST_ACTIONS dispatch + ACTIONS_RESULT round-trip fire within the 4 s timeout
  • Actions render in SwitchListPreference

Note for third-party providers: they also need the inverse — a <queries><package android:name="com.valterc.ki2" /> so their ACTIONS_RESULT reply (sent with setPackage) actually reaches Ki2 under the same rules.

@valterc

valterc commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Hi, thanks for the PR.

I had this plan myself but didn't make it because of Android limitations. I don't think it's a good idea for Ki2 to be handling data from other apps and invoking actions on other apps directly, this goes way of out scope and possibly create side effects that I don't want to deal with.

There are feature requests for Hammerhead to allow extensions to provide custom actions and commands, that would allow this feature without Ki2 having to manage all these registrations and lookup between apps. Otherwise what I might end up doing is just publishing in a broadcast manner, which places the responsibility of handling the commands on the receiving app, not in Ki2.

@cnoons-cyq

Copy link
Copy Markdown
Author

Hi, thanks for your response!

I can understand all of that. I had previously put the initial PR changes together in one go whilst building the first iteration of my app, and long before I'd had a play with the BonusActions in karoo-ext as I only had a karoo 2 on hand.

I was reading the discussions on the BonusAction PR in karoo-ext yesterday and it would be great to see something like BonusTriggers being mapped to BonusActions in Karoo OS. Whilst I'd love to see Hammerhead make that happen, I'd also be keen to see a simple broadcast on button press as well. I completely respect your view of not wanting to handle data from, or invoke actions on other apps.

I'll sit tight and wait to see what happens in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants