Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion android/KMAPro/kMAPro/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ dependencies {
implementation 'com.stepstone.stepper:material-stepper:4.3.1'
api(name: 'keyman-engine', ext: 'aar')
implementation 'io.sentry:sentry-android:7.22.6'
implementation 'androidx.webkit:webkit:1.14.0'
implementation 'androidx.preference:preference:1.2.1'
implementation "com.android.installreferrer:installreferrer:2.2"
implementation 'com.android.installreferrer:installreferrer:2.2'

// Add dependency for generating QR Codes
// (Even though it's embedded in KMEA, because we're manually copying keyman-engine.aar,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/**
* Copyright (C) 2017 SIL International. All rights reserved.
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/

package com.tavultesoft.kmapro;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
Expand Down Expand Up @@ -386,7 +387,7 @@ protected void onResume() {
super.onResume();
if (webView != null) {
if (didFinishLoading) {
String fontFilename = KMManager.getKeyboardTextFontFilename();
String fontFilename = getKeyboardTextFontFilenameOnly();
if (!loadedFont.equals(fontFilename)) {
webView.reload();
}
Expand Down Expand Up @@ -432,8 +433,19 @@ public void onBackPressed() {
}
}

/**
* Returns the filename without path of the display font of the current keyboard.
*/
private String getKeyboardTextFontFilenameOnly() {
String fontPath = KMManager.getKeyboardTextFontFilename();
if (fontPath == null || fontPath.isEmpty()) {
return "";
}
return new File(fontPath).getName();
}

private void loadFont() {
String font = KMManager.getKeyboardTextFontFilename();
String font = getKeyboardTextFontFilenameOnly();
if (!font.isEmpty()) {
loadedFont = font;
String fontUrl = String.format("%s%s", fontBaseUri, font);
Expand Down
1 change: 1 addition & 0 deletions android/KMEA/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
implementation 'commons-io:commons-io:2.16.1'
implementation 'io.sentry:sentry-android:7.22.6'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.webkit:webkit:1.14.0'

// Robolectric
testImplementation 'androidx.test.ext:junit:1.2.1'
Expand Down
173 changes: 93 additions & 80 deletions android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (C) 2017-2018 SIL International. All rights reserved.
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/

package com.keyman.engine;
Expand All @@ -23,6 +23,7 @@
import com.keyman.engine.util.FileUtils;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.util.KMString;
import com.keyman.engine.util.WebViewUtils;

import android.annotation.SuppressLint;
import android.content.Context;
Expand Down Expand Up @@ -83,9 +84,8 @@ final class KMKeyboard extends WebView {
*/
protected static KMManager.BannerType currentBanner = KMManager.BannerType.HTML;

private static String txtFont = "";
private static String oskFont = null;
private static String keyboardRoot = "";
private static String txtFontPath = "";
private static String oskFontPath = "";
private final String fontUndefined = "undefined";
private GestureDetector gestureDetector;
private static ArrayList<OnKeyboardEventListener> kbEventListeners = null;
Expand Down Expand Up @@ -366,8 +366,7 @@ public void loadKeyboard() {
} else {
KMManager.SystemKeyboardWebViewClient.setKeyboardLoaded(false);
}

String htmlPath = "file://" + getContext().getDir("data", Context.MODE_PRIVATE) + "/" + KMManager.KMFilename_KeyboardHtml;
String htmlPath = WebViewUtils.buildAssetUrl(KMManager.KMFilename_KeyboardHtml);
loadUrl(htmlPath);
setBackgroundColor(0);
}
Expand Down Expand Up @@ -538,17 +537,18 @@ protected void toggleSuggestionBanner(HashMap<String, String> associatedLexicalM
* @return String
*/
public static String textFontFilename() {
return txtFont;
return txtFontPath;
}

/**
* Return the full path to the OSK font. Usually used for creating a Typeface font
* @return String
*/
public static String oskFontFilename() {
return oskFont;
return oskFontPath;
}

// REVIEW: this method seems to be unused and undocumented. Can we remove it?
/**
* Return the full path to the special OSK font,
* which is with all the keyboard assets at the root app_data folder
Expand Down Expand Up @@ -634,8 +634,6 @@ public boolean prepareKeyboardSwitch(String packageID, String keyboardID, String
}
String kbKey = KMString.format("%s_%s", languageID, keyboardID);

setKeyboardRoot(packageID);

// Escape single-quoted names for javascript call
keyboardName = keyboardName.replaceAll("\'", "\\\\'"); // Double-escaped-backslash b/c regex.

Expand Down Expand Up @@ -690,28 +688,26 @@ public boolean setKeyboard(String packageID, String keyboardID, String languageI
KMManager.getLatestKeyboardFileVersion(getContext(), packageID, keyboardID) : null;
}

setKeyboardRoot(packageID);

if(kOskFont == null || kOskFont.isEmpty())
kOskFont = kFont;

JSONObject jDisplayFont = makeFontPaths(kFont);
JSONObject jOskFont = makeFontPaths(kOskFont);
JSONObject jDisplayFont = makeFontObject(kFont, packageID);
JSONObject jOskFont = makeFontObject(kOskFont, packageID);

txtFont = getFontFilename(jDisplayFont);
oskFont = getFontFilename(jOskFont);
txtFontPath = getFontFilename(kFont, packageID);
oskFontPath = getFontFilename(kOskFont, packageID);

String kbKey = KMString.format("%s_%s", languageID, keyboardID);

String keyboardPath = makeKeyboardPath(packageID, keyboardID, keyboardVersion);
String keyboardUrl = makeKeyboardUrl(packageID, keyboardID, keyboardVersion);

JSONObject reg = new JSONObject();
try {
reg.put("KN", keyboardName);
reg.put("KI", "Keyboard_" + keyboardID);
reg.put("KLC", languageID);
reg.put("KL", languageName);
reg.put("KF", keyboardPath);
reg.put("KF", keyboardUrl);
reg.put("KP", packageID);

if (jDisplayFont != null) reg.put("KFont", jDisplayFont);
Expand Down Expand Up @@ -796,29 +792,46 @@ private void sendError(String packageID, String keyboardID, String languageID, b
}
}

// Set the base path of the keyboard depending on the package ID
private void setKeyboardRoot(String packageID) {
/**
* Return the root URL for the data folder. Even though this is a local
* location this returns a URL with a magic domain so that it can be
* loaded with fetch() in the webview.
*/
private String getDataRootUrl() {
return WebViewUtils.buildAssetUrl("");
}

/**
* Return the root path for the data folder as a file path. This should be
* used where the file is not loaded through the webview, but is instead
* used by the app directly.
*/
private String getDataRootPath() {
return context.getDir("data", Context.MODE_PRIVATE).toString() + File.separator;
}

private String getPackageRootUrl(String packageID) {
if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) {
this.keyboardRoot = (context.getDir("data", Context.MODE_PRIVATE).toString() +
File.separator + KMManager.KMDefault_UndefinedPackageID + File.separator);
} else {
this.keyboardRoot = (context.getDir("data", Context.MODE_PRIVATE).toString() +
File.separator + KMManager.KMDefault_AssetPackages + File.separator + packageID + File.separator);
return getDataRootUrl() + KMManager.KMDefault_UndefinedPackageID + "/";
}
return getDataRootUrl() + KMManager.KMDefault_AssetPackages + "/" + packageID + "/";
}

public String getKeyboardRoot() {
return this.keyboardRoot;
private String getPackageRootPath(String packageID) {
if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) {
return getDataRootPath() + KMManager.KMDefault_UndefinedPackageID + File.separator;
}
return getDataRootPath() + KMManager.KMDefault_AssetPackages + File.separator + packageID + File.separator;
}

private String makeKeyboardPath(String packageID, String keyboardID, String keyboardVersion) {
String keyboardPath;
private String makeKeyboardUrl(String packageID, String keyboardID, String keyboardVersion) {
String keyboardUrl = getPackageRootUrl(packageID);
if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) {
keyboardPath = getKeyboardRoot() + keyboardID + "-" + keyboardVersion + ".js";
keyboardUrl += keyboardID + "-" + keyboardVersion + ".js";
} else {
keyboardPath = getKeyboardRoot() + keyboardID + ".js";
keyboardUrl += keyboardID + ".js";
}
return keyboardPath;
return keyboardUrl;
}

private void sendKMWError(int lineNumber, String sourceId, String message) {
Expand Down Expand Up @@ -900,42 +913,23 @@ private void saveCurrentKeyboardIndex() {
}

/**
* getFontFilename
* Parse a Font JSON object and return the font filename (ending in .ttf or .otf)
* @param fontObj JSONObject - Font JSON object
* @return String - Filename for the font. If font is invalid, return ""
* Return the full path to the font file. If the font is invalid, return empty string.
* @param font String - Font filename
* @param packageID String - Package ID
* @return String - Full path to the font file. If font is invalid, return "".
*/
private String getFontFilename(JSONObject fontObj) {
String font = "";
if (fontObj == null) {
return font;
private String getFontFilename(String font, String packageID) {
if(font == null || font.equals("")) {
return "";
}
try {
JSONArray sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontFiles);
if (sourceArray != null) {
String fontFile;
int length = sourceArray.length();
for (int i = 0; i < length; i++) {
fontFile = sourceArray.getString(i);
if (FileUtils.hasFontExtension(fontFile)) {
font = fontFile;
break;
}
}
} else {
String fontFile = fontObj.optString(KMManager.KMKey_FontFiles);
if (fontFile != null) {
if (FileUtils.hasFontExtension(fontFile)) {
font = fontFile;
}
}
}
} catch (JSONException e) {
KMLog.LogException(TAG, "", e);
font = "";

if (!FileUtils.hasFontExtension(font)) {
// QUESTION: do we log this?
return "";
}

return font;
String fontRoot = KMManager.isDefaultFont(font) ? getDataRootPath() : getPackageRootPath(packageID);
return fontRoot + font;
}

@SuppressLint("InflateParams")
Expand Down Expand Up @@ -1030,13 +1024,28 @@ public void onDismiss() {
}

/**
* 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`
* Create a JSON object consisting of the font family and the URLs of the
* font files on the local device.
*
* The `font` parameter can either be the filename of the font (with an
* extension recognized as font), or a Font object or JSON string.
* In the former case a new JSON object is created with the font family
* derived from the filename, and the font filename prefixed with path
* to the fonts.
* In the latter case the legacy `sources` key is renamed to `files`.
* If `files` is a single string it will be prefixed with the path to the
* fonts. If `files` is an array, the array is iterated until finding
* the first file with a font extension which is then prefixed with the
* path to the fonts.
*
* @param font A string containing either the font filename or a font
* JSON object as a string
* @param packageID The package ID of the keyboard
*
* @return JSONObject of modified font information with full paths. If font
* is invalid, return `null`.
*/
private JSONObject makeFontPaths(String font) {
private JSONObject makeFontObject(String font, String packageID) {

if(font == null || font.equals("")) {
return null;
Expand All @@ -1047,14 +1056,17 @@ private JSONObject makeFontPaths(String font) {
JSONObject jfont = new JSONObject();
jfont.put(KMManager.KMKey_FontFamily, font.substring(0, font.length()-4));
JSONArray jfiles = new JSONArray();
jfiles.put(keyboardRoot + font);
String fontRoot = KMManager.isDefaultFont(font) ? getDataRootUrl() : getPackageRootUrl(packageID);
jfiles.put(fontRoot + font);
jfont.put(KMManager.KMKey_FontFiles, jfiles);
return jfont;
}

// REVIEW: Why do we need the complicated code below? Can this still
// happen, or can we remove it? (see also getFontFilename)
KMLog.LogInfo(TAG, "Got font without font extension: " + font);

JSONObject fontObj = new JSONObject(font);
JSONArray sourceArray;
String fontFile;

// Replace "sources" key with "files"
if (fontObj.has(KMManager.KMKey_FontSource)) {
Expand All @@ -1064,16 +1076,18 @@ private JSONObject makeFontPaths(String font) {

Object obj = fontObj.get(KMManager.KMKey_FontFiles);
if (obj instanceof String) {
fontFile = fontObj.getString(KMManager.KMKey_FontFiles);
fontObj.put(KMManager.KMKey_FontFiles, keyboardRoot + obj);
String fontFile = fontObj.getString(KMManager.KMKey_FontFiles);
String fontRoot = KMManager.isDefaultFont(fontFile) ? getDataRootUrl() : getPackageRootUrl(packageID);
fontObj.put(KMManager.KMKey_FontFiles, fontRoot + obj);
return fontObj;
} else if (obj instanceof JSONArray) {
sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontFiles);
JSONArray sourceArray = fontObj.optJSONArray(KMManager.KMKey_FontFiles);
if (sourceArray != null) {
for (int i = 0; i < sourceArray.length(); i++) {
fontFile = sourceArray.getString(i);
String fontFile = sourceArray.getString(i);
if (FileUtils.hasFontExtension(fontFile)) {
fontObj.put(KMManager.KMKey_FontFiles, keyboardRoot + fontFile);
String fontRoot = KMManager.isDefaultFont(fontFile) ? getDataRootUrl() : getPackageRootUrl(packageID);
fontObj.put(KMManager.KMKey_FontFiles, fontRoot + fontFile);
fontObj.remove(KMManager.KMKey_FontSource);
return fontObj;
}
Expand All @@ -1082,7 +1096,6 @@ private JSONObject makeFontPaths(String font) {
}
} catch (JSONException e) {
KMLog.LogException(TAG, "Failed to make font for '"+font+"'", e);
return null;
}

return null;
Expand Down
Loading
Loading