From c5b3bafcc7abec1677c0a84c59cdee0957d50615 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 17 Nov 2023 09:54:40 +0700 Subject: [PATCH] change(android): host-page init now retrieves current stub before KMW init --- .../KMEA/app/src/main/assets/android-host.js | 4 + .../keyman/engine/KMKeyboardJSHandler.java | 17 ++++ .../java/com/keyman/engine/data/Keyboard.java | 87 +++++++++++++++++++ web/src/engine/main/src/keyboardInterface.ts | 18 ++-- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index afa2825e33..543e82dca4 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -31,6 +31,10 @@ function init() { keyman.getOskHeight = getOskHeight; keyman.getOskWidth = getOskWidth; keyman.beepKeyboard = beepKeyboard; + + // Readies the keyboard stub for instant loading during the init process. + KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); + keyman.init({ 'embeddingApp':device, 'fonts':'packages/', diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java index 6c959b0056..a8c5291f36 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java @@ -1,6 +1,7 @@ package com.keyman.engine; import android.content.Context; +import android.content.SharedPreferences; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -20,6 +21,7 @@ import android.webkit.JavascriptInterface; import static android.content.Context.VIBRATOR_SERVICE; import com.keyman.engine.KMManager.KeyboardType; +import com.keyman.engine.data.Keyboard; import com.keyman.engine.util.CharSequenceUtil; import com.keyman.engine.util.KMLog; @@ -62,6 +64,21 @@ public class KMKeyboardJSHandler { return kbWidth; } + @JavascriptInterface + public String initialKeyboard() { + // Note: KMManager.getCurrentKeyboard() (and similar) will throw errors until the host-page is first fully + // loaded and has set a keyboard. To allow the host-page to have earlier access, we instead get the stored + // keyboard index directly. + SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + int index = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, 0); + if (index < 0) { + index = 0; + } + + Keyboard kbd = KMManager.getKeyboardInfo(this.context, index); + return kbd.toStub(context); + } + // This annotation is required in Jelly Bean and later: @JavascriptInterface public void beepKeyboard() { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/data/Keyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/data/Keyboard.java index 984284fe09..0dbfe5d023 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/data/Keyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/data/Keyboard.java @@ -16,11 +16,13 @@ import com.keyman.engine.util.FileUtils; import com.keyman.engine.util.KMLog; import com.keyman.engine.util.KMString; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.Serializable; +import java.util.ArrayList; public class Keyboard extends LanguageResource implements Serializable { private static final String TAG = "Keyboard"; @@ -171,6 +173,91 @@ public class Keyboard extends LanguageResource implements Serializable { return o; } + private String getKeyboardRoot(Context context) { + String keyboardRoot = context.getDir("data", Context.MODE_PRIVATE).toString() + + File.separator; + + if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) { + return keyboardRoot + KMManager.KMDefault_UndefinedPackageID + File.separator; + } else { + return keyboardRoot + KMManager.KMDefault_AssetPackages + File.separator + packageID + File.separator; + } + } + + public String getKeyboardPath(Context context) { + String keyboardID = this.getKeyboardID(); + String keyboardVersion = this.getVersion(); + if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) { + return getKeyboardRoot(context) + keyboardID + "-" + keyboardVersion + ".js"; + } else { + return getKeyboardRoot(context) + keyboardID + ".js"; + } + } + + public String toStub(Context context) { + JSONObject stubObj = new JSONObject(); + + try { + stubObj.put("KN", this.getKeyboardName()); + stubObj.put("KI", "Keyboard_" + this.getKeyboardID()); + stubObj.put("KLC", this.getLanguageID()); + stubObj.put("KL", this.getLanguageName()); + stubObj.put("KF", this.getKeyboardPath(context)); + stubObj.put("KP", this.getPackageID()); + + String displayFont = this.getFont(); + if(displayFont != null) { + stubObj.put("KFont", this.buildDisplayFontObject(displayFont, context)); + } + + String oskFont = this.getOSKFont(); + if(oskFont != null) { + stubObj.put("KOskFont", this.buildDisplayFontObject(oskFont, context)); + } + + String displayName = this.getDisplayName(); + if(displayName != null) { + stubObj.put("displayName", displayName); + } + + return stubObj.toString(); + } catch(JSONException e) { + KMLog.LogException(TAG, "", e); + return null; + } + } + + /** + * Take a font JSON object and adjust to pass to JS + * 1. Replace "source" keys for "files" keys + * 2. Create full font paths for .ttf or .svg + * @param font String font JSON object as a string + * @return JSONObject of modified font information with full paths. If font is invalid, return `null` + */ + private JSONObject buildDisplayFontObject(String font, Context context) { + if(font == null || font.equals("")) { + return null; + } + + String keyboardRoot = this.getKeyboardRoot(context); + + try { + if (FileUtils.hasFontExtension(font)) { + JSONObject jfont = new JSONObject(); + jfont.put(KMManager.KMKey_FontFamily, font.substring(0, font.length() - 4)); + JSONArray jfiles = new JSONArray(); + jfiles.put(keyboardRoot + font); + jfont.put(KMManager.KMKey_FontFiles, jfiles); + return jfont; + } else { + return null; + } + } catch (JSONException e) { + KMLog.LogException(TAG, "Failed to make font for '"+font+"'", e); + return null; + } + } + /** * Get the fallback keyboard. If never specified, use sil_euro_latin * @param context Context diff --git a/web/src/engine/main/src/keyboardInterface.ts b/web/src/engine/main/src/keyboardInterface.ts index 305373aea3..971bcdcab5 100644 --- a/web/src/engine/main/src/keyboardInterface.ts +++ b/web/src/engine/main/src/keyboardInterface.ts @@ -88,15 +88,21 @@ export default class KeyboardInterface { + const pathConfig = this.engine.config.paths; + return new KeyboardStub(Pstub, pathConfig.keyboards, pathConfig.fonts); + }; if(!this.engine.config.deferForInitialization.hasFinalized) { - this.engine.config.deferForInitialization.then(() => this.engine.keyboardRequisitioner.cache.addStub(stub)); + // pathConfig is not ready until KMW initializes, which prevents proper stub-building. + this.engine.config.deferForInitialization.then(() => this.engine.keyboardRequisitioner.cache.addStub(buildStub())); } else { + const stub = buildStub(); + + if(this.engine.keyboardRequisitioner?.cache.findMatchingStub(stub)) { + return 1; + } this.engine.keyboardRequisitioner.cache.addStub(stub); }