Skip to content

fix(android): load KMP files from app-external sources - #16392

Open
jahorton wants to merge 3 commits into
masterfrom
fix/android/load-external-kmp-file
Open

fix(android): load KMP files from app-external sources#16392
jahorton wants to merge 3 commits into
masterfrom
fix/android/load-external-kmp-file

Conversation

@jahorton

@jahorton jahorton commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

This PR provides two main services:

  • It allows on-device KMP file providers (such as Chrome downloads or the local Files app) to smoothly pass those KMP files into Keyman for Android for installation.
  • It also removes wildcard-host http / https deep-links from the app manifest, as these are invalid.

Relates-to: #14854

Upon my investigation into related documentation, I've determined that with current versions of Android, setting up a general, "anywhere on the web" deep link for KMP files is not supported and should not be attempted.

How, then, do we facilitate external apps passing off KMP links or files to Keyman for Android? Turns out... we've already done the work for downloaded files with file: and content: scheme deep-links. The issue is that some of our permissions-checking code logic was bad. With a little work to correct them - removing the bad checks that never should have existed anyway - the files load nicely!

Build-bot: skip release:android

Notes: upon reviewing #10133 and #14122, it sounds like there may be variance among devices. Samsung is likely to be trickier to handle, while Google devices using plain Chrome may be more straightforward. I may need to alter the user tests accordingly.

User Testing

GROUP_API_28: Perform these tests on devices with Android API 28 (Android 9).
GROUP_API_30: Perform these tests on devices with Android API 30 (Android 11).
GROUP_API_33: Perform these tests on devices with Android API 33+ (Android 13).

TEST_DOWNLOAD_AND_INSTALL: Using Keyman for Android, download a KMP from within the Chrome app, then install it from Chrome's download section.

  • Navigate to https://jahorton.github.io and click the english_punct_rota.kmp link in order to download the file.
  • Once downloaded, go to the download section (if not displayed) and tap on the downloaded file.
  • Verify that Keyman for Android launches and opens the package installer without displaying an error or alert.
  • Install the package and verify that no errors result.

TEST_INSTALL_VIA_FILES: Using Keyman for Android, download a KMP from within the Chrome app, then install it from the device's Files app.

  • Navigate to https://jahorton.github.io and click the gesture_prototyping.kmp link in order to download the file.
  • Once downloaded, exit Chrome and use the device's Files app to find and "open" the downloaded file.
  • Verify that Keyman for Android launches and opens the package installer without displaying an error or alert.
  • Install the package and verify that no errors result.

This PR provides two main services:
- It allows on-device KMP file providers (such as Chrome downloads or the local Files app) to smoothly pass those KMP files into Keyman for Android for installation.
- It also removes wildcard-host http / https deep-links from the app manifest, as these are invalid.

Fixes: #14854
Fixes: #10133

Upon my investigation into related documentation, I've determined that with current versions of Android, setting up a general, "anywhere on the web" deep link for KMP files is not supported and should not be attempted.

How, then, do we facilitate external apps passing off KMP links or files to Keyman for Android?  Turns out... we've already done the work for downloaded files with `file:` and `content:` scheme deep-links.  The issue is that some of our permissions-checking code logic was bad.   With a little work to correct them - removing the bad checks that never should have existed anyway - the files load nicely!

Build-bot: skip release:android
@keymanapp-test-bot

keymanapp-test-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

User Test Results

Test specification and instructions

  • ⬜ GROUP_API_28: Perform these tests on devices with Android API 28 (Android 9).

    • TEST_DOWNLOAD_AND_INSTALL (PASSED) (notes)
    • TEST_INSTALL_VIA_FILES (OPEN)
  • ⬜ GROUP_API_30: Perform these tests on devices with Android API 30 (Android 11).

    • TEST_DOWNLOAD_AND_INSTALL (PASSED) (notes)
    • TEST_INSTALL_VIA_FILES (OPEN)
  • ⬜ GROUP_API_33: Perform these tests on devices with Android API 33+ (Android 13).

    • TEST_DOWNLOAD_AND_INSTALL (PASSED) (notes)
    • TEST_INSTALL_VIA_FILES (OPEN)
Results Template
# Test Results

### GROUP_API_28: Perform these tests on devices with Android API 28 (Android 9).

* **TEST_INSTALL_VIA_FILES (OPEN):** notes

### GROUP_API_30: Perform these tests on devices with Android API 30 (Android 11).

* **TEST_INSTALL_VIA_FILES (OPEN):** notes

### GROUP_API_33: Perform these tests on devices with Android API 33+ (Android 13).

* **TEST_INSTALL_VIA_FILES (OPEN):** notes

Test Artifacts

@keymanapp-test-bot keymanapp-test-bot Bot added this to the A19S36 milestone Aug 17, 2026
@mcdurdin

Copy link
Copy Markdown
Member

Trying to install a .kmp on my phone from Samsung files gives an error

Screenshot_20260818_072051_My Files.jpg

@mcdurdin

Copy link
Copy Markdown
Member

I tested downloading a file from Chrome on my Samsung Galaxy A553 5G on Android 16 (One UI 8.0), Chrome version 151.0.7922.137 (11 Aug 2026). It worked fine -- Keyman appeared on the list of available apps to open the .kmp file, and no errors were encountered.

Samsung Files continues to fail with the error as shown above. 'Files by Google' works fine -- so suggest we recommend that as an alternative file manager on Samsung devices.

See also: #10967 (comment)

@mcdurdin

Copy link
Copy Markdown
Member

I think we should have a user test for:

  • installing a keyboard from keyman.com (in a browser, not within Keyman)

We should at some point also test, once we have a stable release build:

  • the normal keyboard + app install flow from keyman.com (via Play Store)

@jahorton

Copy link
Copy Markdown
Contributor Author

I think we should have a user test for:

* installing a keyboard from keyman.com (in a browser, not within Keyman) 

Would the test be for it to download, then install from download? Or would it be to instantly load into the Keyman app package-install process?

@mcdurdin

Copy link
Copy Markdown
Member

Or would it be to instantly load into the Keyman app package-install process?

I think this -- we want to verify that existing package install pathways have not been impacted (not that I think they have -- but for the avoidance of unpleasant surprises...)

@jahorton

jahorton commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Or would it be to instantly load into the Keyman app package-install process?

I think this -- we want to verify that existing package install pathways have not been impacted (not that I think they have -- but for the avoidance of unpleasant surprises...)

They were, but by other changes - we internationalized keyman.com, which prefixes the language code onto all paths. I'm working on this now.

@mcdurdin

Copy link
Copy Markdown
Member

They were, but by other changes - we internationalized keyman.com, which prefixes the language code onto all paths. I'm working on this now.

Can you open an issue as a first step?

@mcdurdin mcdurdin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AndroidManifest.xml still seems a bit chaotic. It would be good to review each intent-filter and ensure that the comments make sense, and group them appropriately.

Comment on lines 40 to 43
// No special permissions are needed.
// - https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
// - https://stackoverflow.com/a/73630987
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just delete the empty else statement. We can have a note in the commit history for the change.

Comment on lines +137 to +139
<data
android:mimeType="application/vnd.keyman"
android:scheme="content" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a registered mime type.

Suggested change
<data
android:mimeType="application/vnd.keyman"
android:scheme="content" />

Comment on lines +146 to +148
<data
android:mimeType="application/vnd.keyman.kmp+zip"
android:scheme="content" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated

Suggested change
<data
android:mimeType="application/vnd.keyman.kmp+zip"
android:scheme="content" />

Comment on lines 212 to 215
<data
android:host="keyman-staging.com"
android:scheme="http"
android:pathPrefix="/keyboards/install" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is http

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the staging server usually https: when live? If so, yeah, I should update that.

I inferred the http:// because of existing entries like this one:

<data
android:host="keyman-staging.com"
android:scheme="http"
android:pathPrefix="/go/package/download" />

Because of how AndroidManifest.xml works, if there was a separate https: host set within the same <intent-filter>, it would also check against AndroidManifest.xml. That's relying on quirky, unintuitive behaviors; I think we'd prefer to be explicit here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the staging server usually https: when live? If so, yeah, I should update that.

Always https:, yes. And generally always live.

</intent-filter>

<intent-filter android:priority="50">
<intent-filter android:priority="50" android:autoVerify="true">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this change for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.android.com/training/app-links/verify-applinks

We probably don't need to implement this because we have another <intent-filter> entry that does verify the same domains... but that's a happy accident and shouldn't be what we rely upon.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, please make sure you call out changes like this in the PR description because it's definitely not related to the core purpose of the PR, and it is a significant change!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, can you please split this into a separate PR for test and review? There's a fair bit of detail there that we should be careful about changing and testing.

Comment thread android/KMAPro/kMAPro/src/main/AndroidManifest.xml Outdated
Comment thread android/KMAPro/kMAPro/src/main/AndroidManifest.xml Outdated
<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="file" />
<data android:scheme="content" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches both file:/// and content:// URIs. Often times, what we get between apps appears to be the content:// one.

It's an OR, not an AND - allowing us to share the host and pathPattern entries for both protocols.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what this is fixing though -- does this impact the underlying reported issue?

Comment thread android/KMAPro/kMAPro/src/main/AndroidManifest.xml Outdated
@jahorton
jahorton requested a review from mcdurdin August 19, 2026 17:08
@Meng-Heng

Meng-Heng commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Test Results

GROUP_API_28:

Test Specs

  1. Pixel 3a XL, API 28 (Android 9)
  2. keyman-19.0.268-alpha-test-16392.apk
  • TEST_DOWNLOAD_AND_INSTALL (PASSED):
  1. Navigate to https://jahorton.github.io and download the english_punct_rota.kmp file.
  2. Once downloaded -> Open the file location -> Tap on the downloaded file -> Allow "Full Access to Files"
  3. VERIFIED: Keyman for Android launches and opens the package installer without displaying an error or alert
  4. Follow through the installation dialogs of the keyboard package
  5. VERIFIED: No errors result. Dictionary installed alongside the keyboard.
  6. Typing and predictive text work as expected.

GROUP_API_30:

Test Specs

  1. Pixel 5, API 30 (Android 11)
  2. keyman-19.0.268-alpha-test-16392.apk
  • TEST_DOWNLOAD_AND_INSTALL (PASSED):
  1. Navigate to https://jahorton.github.io and download the english_punct_rota.kmp file.
  2. Once downloaded -> Open the file location -> Tap on the downloaded file -> Receive an error about "Storage Permission"
  3. Go to Android Settings -> Permission Manager -> Files and media permission -> Allow files and media only for Keyman
  4. Tap on the downloaded file
  5. VERIFIED: Keyman for Android launches and opens the package installer without displaying an error or alert
  6. Follow through the installation dialogs of the keyboard package
  7. VERIFIED: No errors result. Dictionary installed alongside the keyboard.
  8. Typing and predictive text work as expected.

GROUP_API_33:

Test Specs

  1. Pixel 6a, API 33 (Android 13.0)
  2. keyman-19.0.268-alpha-test-16392.apk
  • TEST_DOWNLOAD_AND_INSTALL (PASSED):
  1. Launch and Exit Keyman
  2. Navigate to https://jahorton.github.io and download the english_punct_rota.kmp file.
  3. Once downloaded -> Open the file location -> Tap on the downloaded file
  4. VERIFIED: Keyman for Android launches and opens the package installer without displaying an error or alert
  5. Follow through the installation dialogs of the keyboard package
  6. VERIFIED: No errors result. Dictionary installed alongside the keyboard.
  7. Predictive text and typing work as expected.
  • TEST_INSTALL_VIA_FILES (OPEN):

@Meng-Heng

Meng-Heng commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Hello, @jahorton. Could you help confirm the steps for the TEST_INSTALL_VIA_FILES are the same as TEST_DOWNLOAD_AND_INSTALL?

@jahorton

Copy link
Copy Markdown
Contributor Author

Hello, @jahorton. Could you help confirm the steps for the TEST_INSTALL_VIA_FILES are the same as TEST_DOWNLOAD_AND_INSTALL?

Sorry about that. I've updated the instructions for that test now.

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

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants