diff --git a/HISTORY.md b/HISTORY.md
index 9de791f041..03bd4cf1a8 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,9 @@
# Keyman Version History
+## 19.0.10 alpha 2025-03-01
+
+* chore: merge beta into master B18S2 (#13391)
+
## 19.0.9 alpha 2025-02-27
* docs: move node.js before Emscripten (#13366)
@@ -42,6 +46,102 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
+## 18.0.203 beta 2025-02-28
+
+* feat(windows): keyman configuration icon refresh (#13294)
+* fix(windows): if kmshell is "silent" don't start a Keyman update (#13330)
+* chore(windows): Update crowdin strings for Khmer (#13364)
+* docs: clean up links in builder.md (#13328)
+* chore: add build.sh edit action for Delphi projects (#13327)
+* fix(developer): handle line breaks in debugger correctly (#13334)
+* fix(developer): use U+0001 for deadkey markers in the debugger (#13336)
+* feat(developer): use .kps folder as initial dir for Add Files (#13324)
+* fix(developer): verify `context(n)` offsets are valid in kmcmplib (#13308)
+* chore(core): add unit tests to validate `nul` with `index` and `context` (#13311)
+* fix(core): include `nul` in context offset calculations in kmx processor (#13314)
+* fix(developer): include `nul` in offset calculations for kmw v10 compiler (#13326)
+
+## 18.0.202 beta 2025-02-27
+
+* fix(android): Direct user to switch system IME when WebView fails (#13352)
+* fix(android): Filter out SocketTimeoutExceptions (#13353)
+* refactor(linux): extract method for setting up mocks in tests (#13355)
+* chore(linux): update to current Debian policy version (#13362)
+* fix(developer): prevent double action of editor hotkeys in debugger (#13345)
+* fix(linux): ignore DBusException during install and uninstall (#13356)
+* fix(linux): ignore exception when fcitx5-configtool is not found (#13363)
+* fix(developer): prevent simultaneous opening of the same project by two processes (#13341)
+* chore: add a workflow to automatically close linked issues (#13369)
+
+## 18.0.201 beta 2025-02-26
+
+* fix(web): prevent cookie-default behavior from mangling engine init (#13331)
+* chore(linux): adjust to renamed xdg module (#13354)
+
+## 18.0.200 beta 2025-02-25
+
+* fix(web): properly support workerless use of Web engine (#13290)
+* fix(web): reconnect, patch up engine/main headless unit tests (#13291)
+* change(ios): re-order host page script load so Sentry can report script load failures (#13332)
+* chore(linux): Update to new Debian policy version (#13325)
+
+## 18.0.199 beta 2025-02-24
+
+* fix(windows): Update Greek Polytonic for keyboard help (#13323)
+
+## 18.0.198 beta 2025-02-21
+
+* fix(android): Update system keyboard height consistently (#13273)
+
+## 18.0.197 beta 2025-02-20
+
+* fix(linux): address compiling with ICU 76 (#13285)
+
+## 18.0.196 beta 2025-02-19
+
+* fix(windows): remove sentry report on firstrun in idle state (#13266)
+* fix(android): Correct the scope of `tier` in build.gradle (#13268)
+* chore(common): Update crowdin strings for Polytonic Greek (#13277)
+* fix(web): provide informative error + message when loading UI without the engine (#13269)
+* fix(developer): Use 'desktop' for target in OSK fill temp keyboard (#13271)
+* fix(linux): add missing dependency for uploading to llso (#13279)
+* refactor(linux): re-enable integration tests (#13234)
+
+## 18.0.195 beta 2025-02-18
+
+* docs(web): use promise-chaining in manual-control example (#13272)
+
+## 18.0.194 beta 2025-02-17
+
+* fix(android): Use TIER.md to determine Play Store tier (#13259)
+* fix(android): prevent update-checks from crashing system keyboard and app if `DownloadManager` is disabled (#13218)
+* feat(linux): add `--remote-debug` flag to `run-tests.sh` script (#13233)
+* chore(linux): improve output when running tests (#13247)
+* refactor(linux): cleanup code of test fixture (#13248)
+
+## 18.0.193 beta 2025-02-14
+
+* chore(developer): add INFO messages for `kmc copy` and `kmc generate` (#13200)
+* fix(android): Remove sendOptionsToKeyboard function from Engine API (#13219)
+* docs(android): Document removing sendOptionsToKeyboard API (#13220)
+* chore(ios): increase minimum ios version to 15 (#13201)
+* fix(android): Catch WebView exceptions and prompt user to install WebView (#13140)
+* chore(common): Update crowdin strings for Mon (#13241)
+* chore(ios,windows): Update Crowdin strings for Czech (#13242)
+* change(web): drop 'dom' unit test concurrency when run in CI mode (#13141)
+* chore(web): ensure /web/build.sh clean triggers all predictive-text child actions (#13158)
+* chore(web): fix coverage-reporting artifact (#13196)
+
+## 18.0.192 beta 2025-02-14
+
+* chore(common): Fix 17.0.335 tier in HISTORY.md (#13213)
+* docs(linux): update what's new for 18.0 (#13207)
+* chore(linux): additional code cleanup after Focal removal (#13206)
+* docs(windows): update keyman for windows 18.0 whats-new (#13211)
+* chore: undo master merge (#13238)
+* change(ios): prevent memory leaks from keyboard-related notification observers (#13129)
+* change(ios): mitigate keyboard input-view memory leak by force-discarding WebView (#13169)
+
## 18.0.191 beta 2025-02-12
* feat(windows): handle a hard windows reset occurring while downloading updated keyman files (#13128)
diff --git a/VERSION.md b/VERSION.md
index 0394531a29..a88b08f552 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.10
\ No newline at end of file
+19.0.11
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle
index 11ec4efd2d..768d98d933 100644
--- a/android/KMAPro/kMAPro/build.gradle
+++ b/android/KMAPro/kMAPro/build.gradle
@@ -9,6 +9,8 @@ plugins {
ext.rootPath = '../../'
apply from: "$rootPath/version.gradle"
+String tier = new File('../../TIER.md').getText('UTF-8').trim();
+
android {
compileSdk 34
namespace="com.tavultesoft.kmapro"
@@ -75,7 +77,7 @@ android {
// - PR builds also have "-test-1234" suffix (where 1234 is PR#)
// Gradle sync will nullify these values, so have a fallback
String appSuffix = ''
- String tag = VERSION_TAG ? VERSION_TAG : '-' + new File('../../TIER.md').getText('UTF-8');
+ String tag = VERSION_TAG ? VERSION_TAG : '-' + tier;
if (tag.startsWithAny("-beta", "beta")) {
appSuffix = ' Beta';
} else if (tag.startsWithAny("-alpha", "alpha")) {
@@ -127,7 +129,7 @@ if (env_keys_json_file != null && file(env_keys_json_file).exists()) {
// Deactivate lower conflicting version APKs
resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.IGNORE)
- switch (System.env.TIER) {
+ switch (tier) {
case 'beta':
track.set("beta")
println "TIER set to beta"
diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
index 5ca3de5807..05c82082ad 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
@@ -4,6 +4,7 @@
package com.keyman.android;
+import com.keyman.engine.util.DownloadFileUtils;
import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
import com.tavultesoft.kmapro.BuildConfig;
import com.tavultesoft.kmapro.DefaultLanguageResource;
@@ -84,7 +85,12 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false);
KMManager.setHapticFeedback(mayHaveHapticFeedback);
- KMManager.executeResourceUpdate(this);
+ // Checking for updates should never be allowed to crash the keyboard.
+ // Just silently fail if this occurs.
+ if(DownloadFileUtils.getDownloadManager(this) != null) {
+ // Will try to emit a toast if it fails - i.e., is not silent.
+ KMManager.executeResourceUpdate(this);
+ }
}
@Override
@@ -149,16 +155,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
KMManager.onStartInput(attribute, restarting);
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM);
- // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes,
- // making it an important time to detect orientation changes.
Context appContext = getApplicationContext();
- int newOrientation = KMManager.getOrientation(appContext);
- if(newOrientation != lastOrientation) {
- lastOrientation = newOrientation;
- Configuration newConfig = this.getResources().getConfiguration();
- KMManager.onConfigurationChanged(newConfig);
- }
-
// Temporarily disable predictions on certain fields (e.g. hidden password field or numeric)
int inputType = attribute.inputType;
KMManager.setMayPredictOverride(inputType);
@@ -228,6 +225,16 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
+ // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes,
+ // making it an important time to detect orientation changes.
+ Context appContext = getApplicationContext();
+ int newOrientation = KMManager.getOrientation(appContext);
+ if(newOrientation != lastOrientation) {
+ lastOrientation = newOrientation;
+ Configuration newConfig = this.getResources().getConfiguration();
+ KMManager.onConfigurationChanged(newConfig);
+ }
+
// We should extend the touchable region so that Keyman sub keys menu can receive touch events outside the keyboard frame
Point size = KMManager.getWindowSize(getApplicationContext());
@@ -250,8 +257,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
if (exText != null)
exText = null;
}
- // Initialize keyboard options
- KMManager.sendOptionsToKeyboard();
}
@Override
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
index e79cb917ff..596c247d09 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
@@ -133,7 +133,6 @@ public class AdjustLongpressDelayActivity extends BaseActivity {
// Store the longpress delay as a reference
// and then update KeymanWeb with the longpress delay
KMManager.setLongpressDelay(currentDelayTimeMS);
- KMManager.sendOptionsToKeyboard();
super.onBackPressed();
}
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java
index 4410761e6d..e66d5e9ebc 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KMPBrowserActivity.java
@@ -11,6 +11,7 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -70,7 +71,13 @@ public class KMPBrowserActivity extends BaseActivity {
}
}
- setContentView(R.layout.activity_kmp_browser);
+ try {
+ setContentView(R.layout.activity_kmp_browser);
+ } catch (AndroidRuntimeException e) {
+ // WebView not enabled
+ finish();
+ return;
+ }
webView = (WebView) findViewById(R.id.kmpBrowserWebView);
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index 908d2f4538..989c7716a6 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -43,7 +43,9 @@ import com.keyman.android.DownloadIntentService;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.util.KMPLink;
import com.keyman.engine.util.KMString;
+import com.keyman.engine.util.WebViewUtils;
import com.keyman.engine.util.WebViewUtils.EngineWebViewVersionStatus;
+import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
import android.Manifest;
import android.app.ProgressDialog;
@@ -153,7 +155,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.setDebugMode(true);
}
+ // Verify WebView installed and enabled before attempting to initialize KMManager
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
+
KMManager.executeResourceUpdate(this);
DefaultLanguageResource.install(context);
@@ -181,8 +185,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
textView.setTextSize((float) textSize);
textView.setSelection(textView.getText().length());
- // Use in-app keyboard WebView to check the Chrome version
- checkChromeVersion(KMManager.getKMKeyboard(KeyboardType.KEYBOARD_TYPE_INAPP));
+ // Check WebView enabled and Chrome version
+ SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context);
+ EngineWebViewVersionStatus chromeStatus = KMManager.getEngineWebViewVersionStatus();
+ updateWebViewIfNeeded(chromeStatus, webViewStatus);
+
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
checkGetStarted();
}
@@ -475,8 +482,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
@Override
public void onKeyboardLoaded(KeyboardType keyboardType) {
- // Initialize keyboard options
- KMManager.sendOptionsToKeyboard();
+ // Do nothing
}
@Override
@@ -770,39 +776,114 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.setHapticFeedback(mayHaveHapticFeedback);
}
- private void checkChromeVersion(WebView webView) {
- if (KMManager.getEngineWebViewVersionStatus() != EngineWebViewVersionStatus.FULL) {
- LinearLayout updateChromeLayout = (LinearLayout) findViewById(R.id.updateChromeLayout);
- updateChromeLayout.setVisibility(View.VISIBLE);
-
- Button updateChromeButton = (Button)findViewById(R.id.updateChromeButton);
- updateChromeButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // Launch PlayStore to update Chrome
- try {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
- }
- }
- } catch (android.content.ActivityNotFoundException e) {
- // Link to Chrome if user is not signed in to Play Store
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
- }
- }
- }
- });
+ /**
+ * Based on whether Chrome and WebView are installed & enabled,
+ * display the corresponding update panel
+ * @param chromeStatus
+ * @param webViewStatus
+ */
+ private void updateWebViewIfNeeded(EngineWebViewVersionStatus chromeStatus, SystemWebViewStatus webViewStatus) {
+ if (chromeStatus == EngineWebViewVersionStatus.FULL && webViewStatus == SystemWebViewStatus.FULL) {
+ return;
}
+
+ if (webViewStatus == SystemWebViewStatus.NOT_INSTALLED) {
+ displayInstallWebView();
+ } else if (webViewStatus == SystemWebViewStatus.DISABLED) {
+ displayEnableWebView();
+ } else {
+ // Otherwise, display update Chrome
+ displayUpdateChrome();
+ }
+ }
+
+ private void displayInstallWebView() {
+ TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
+ textView.setText(getString(R.string.body_install_webview));
+
+ Button button = (Button)findViewById(R.id.webViewChromeButton);
+ button.setText(getString(R.string.label_install_webview));
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Play Store link to install WebView
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.webview"));
+
+ // Launch intent
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent);
+ }
+ } catch (android.content.ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
+ webViewChromeLayout.setVisibility(View.VISIBLE);
+ }
+
+ private void displayEnableWebView() {
+ TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
+ textView.setText(getString(R.string.body_enable_webview));
+
+ Button button = (Button)findViewById(R.id.webViewChromeButton);
+ button.setText(getString(R.string.label_enable_webview));
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Application settings to enable WebView
+ Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS",
+ Uri.parse("package:com.google.android.webview"));
+
+ // Launch intent
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent);
+ }
+ } catch (android.content.ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
+ webViewChromeLayout.setVisibility(View.VISIBLE);
+ }
+
+ private void displayUpdateChrome() {
+ // TextView's default string is to update Chrome
+
+ Button button = (Button)findViewById(R.id.webViewChromeButton);
+ button.setText(getString(R.string.button_update_chrome));
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Primary Play Store link to install/update Chrome
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.android.chrome"));
+
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent);
+ } else {
+ // (alternate Play Store link to install/update Chrome)
+ intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
+
+ intent.resolveActivity(packageManager);
+ startActivity(intent);
+ }
+ } catch (android.content.ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
+ webViewChromeLayout.setVisibility(View.VISIBLE);
}
private Uri requestPermissionIntentUri;
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
index 87c7fe16f9..c2911272d1 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
@@ -11,7 +11,7 @@
-
-
+