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

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
This commit is contained in:
Joshua Horton 2026-08-17 15:33:30 -05:00
parent f9b083b0fa
commit f4a67d8866
2 changed files with 6 additions and 61 deletions

View file

@ -113,62 +113,6 @@
android:scheme="content" />
</intent-filter>
<!--
Capture file open requests (pathPattern is honoured) where no
MIME type is provided in the Intent. An Intent with a null
MIME type will never be matched by a filter with a set MIME
type, so we need a second intent-filter if we wish to also
match files with this extension and a non-null MIME type
(even if it is non-null but zero length).
-->
<intent-filter android:priority="50">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:host="*" />
<data android:pathPattern="/.*\\.kmp" />
</intent-filter>
<!--
Capture file open requests (pathPattern is honoured) where a
(possibly blank) MIME type is provided in the Intent. This
filter may only be necessary for supporting ES File Explorer,
which has the probably buggy behaviour of using an Intent
with a MIME type that is set but zero-length. It's
impossible to match such a type except by using a global
wildcard.
-->
<intent-filter android:priority="50">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern="/.*\\.kmp" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- http:// and https:// protocols -->
<data
android:host="*"
android:pathPattern="/.*\\.kmp"
android:scheme="http" />
<data
android:host="*"
android:pathPattern="/.*\\.kmp"
android:scheme="https" />
</intent-filter>
<intent-filter>
<!-- keyman:download// deep linking to https://keyman.com/keyboards/ -->
<action android:name="android.intent.action.VIEW" />

View file

@ -33,12 +33,13 @@ public class CheckPermissions {
// API 30-32
permissionsOK = Environment.isExternalStorageManager() ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
}
else {
// API 33+
// We had to remove these MEDIA permissions from AndroidManifest.xml so these will end up failing
// https://support.google.com/googleplay/android-developer/answer/14115180?hl=en
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_IMAGES);
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_VIDEO);
// No special permissions are needed.
// - https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
// - https://stackoverflow.com/a/73630987
}
return permissionsOK;