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 @@ - - +