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