diff --git a/HISTORY.md b/HISTORY.md
index 5a5b197557..538bb5da2e 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,40 @@
# Keyman Version History
+## 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 60181cc347..6a43aea978 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.192
\ No newline at end of file
+18.0.196
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle
index 11ec4efd2d..94a29347b7 100644
--- a/android/KMAPro/kMAPro/build.gradle
+++ b/android/KMAPro/kMAPro/build.gradle
@@ -67,6 +67,7 @@ android {
productFlavors {
}
+ String tier = new File('../../TIER.md').getText('UTF-8').trim();
applicationVariants.all { variant ->
// Append tag to app name to help differentiate versions for app testers:
// - stable builds have no suffix
@@ -75,7 +76,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 +128,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 (String.valueOf(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..a59f81dbc9 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
@@ -250,8 +256,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 @@
-
-
+