From 4e699ae573b30a33cd4051a637d32870b7dc6862 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 24 Sep 2026 08:23:58 +0200 Subject: [PATCH] fix(android): refresh current keyboard when resuming main activity If the main activity was paused, e.g. to open the keyboard picker, then it would lost track of the last known keyboard, which meant that the menu would show details for the wrong keyboard. Add resume logic to refresh last known keyboard and as a side benefit, fix an unreported bug with the text area font getting out of sync in the same way. Also ensure that the keyboard data is updated before the drawer starts to open, to prevent a delayed change to the active keyboard label. As part of this, discovered a re-entrancy issue with KMLog, which would try and collect debug data, which could itself trigger another KMLog call. Did some basic cleanup of that, but more could be done in a future iteration. Noted a number of minor cleanup issues for resolution in v20. The API has some assumptions around logging and use which should be tightened up in a future release: in general, the API should not be logging invalid parameters or state: it should return an error response to the caller, which should be responsible for logging the issue, if it wants to. The Engine should only log if there is something internally inconsistent. Fixes: #16601 --- .../com/tavultesoft/kmapro/MainActivity.java | 48 +++++++++++++------ .../java/com/keyman/engine/KMManager.java | 8 ++-- .../engine/data/KeyboardController.java | 15 ++---- .../java/com/keyman/engine/util/KMLog.java | 1 + 4 files changed, 42 insertions(+), 30 deletions(-) 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 044f21abba..7ee108eba4 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 @@ -437,17 +437,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene protected void onResume() { super.onResume(); - if (navigationView != null) { - updateCurrentKeyboardDrawerItemTitle(navigationView); - updateInstalledLanguagesDrawerTitle(navigationView); - initializeDrawerItemSubtitles(navigationView); - } - - if (textView != null) { - // Reset inAppPredictionsSuspendedForSensitiveInput flag - KMManager.setPredictionsSuspended(textView.getInputType(), KeyboardType.KEYBOARD_TYPE_INAPP); - } - KMManager.onResume(); KMManager.hideSystemKeyboard(); @@ -465,12 +454,35 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } resizeTextView(textView.isKeyboardVisible()); + // The keyboard may have been changed while we were paused, for example if + // the keyboard picker activity is started. + com.keyman.engine.data.Keyboard currentKeyboard = KMManager.getCurrentKeyboardInfo(context); + if(currentKeyboard != null) { + // TODO(v20): eliminate `lastKnownKeyboardId` and always use `currentKeyboard.getKeyboardID()` + // TODO(v20): this lines up with onKeyboardChanged(), refactor accordingly + lastKnownKeyboardId = currentKeyboard.getKeyboardID(); + } + + if (navigationView != null) { + // TODO(v20): these three calls are repeated in various places and should be cleaned up + updateCurrentKeyboardDrawerItemTitle(navigationView); + updateInstalledLanguagesDrawerTitle(navigationView); + initializeDrawerItemSubtitles(navigationView); + } + + if (textView != null) { + // Reset inAppPredictionsSuspendedForSensitiveInput flag + KMManager.setPredictionsSuspended(textView.getInputType(), KeyboardType.KEYBOARD_TYPE_INAPP); + textView.setTypeface(KMManager.getKeyboardTextFontTypeface(this)); + } + KMManager.addKeyboardEventListener(this); KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); PackageActivity.addKeyboardDownloadEventListener(this); CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context); + // TODO(v20): move loadingIntentUri logic into a separate function Intent intent = getIntent(); Uri loadingIntentUri = intent.getData(); @@ -663,6 +675,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene if (drawerLayout.isDrawerOpen(GravityCompat.END)) { drawerLayout.closeDrawer(GravityCompat.END); } else { + updateCurrentKeyboardDrawerItemTitle(navigationView); + updateInstalledLanguagesDrawerTitle(navigationView); + initializeDrawerItemSubtitles(navigationView); drawerLayout.openDrawer(GravityCompat.END); } return true; @@ -866,8 +881,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene * If URL is valid, download the kmp. * @param packageUri URI to download the package. * @param installMode KMP installation mode (silent, welcome only, or full) - * TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for + * TODO(v20): only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for * URL parsing, not this function. + * TODO(v20): move this into a separate module */ public void downloadKMP(Uri packageUri, KmpInstallMode installMode) { if (packageUri == null) { @@ -983,6 +999,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold); } + // TODO(v20): remove remaining tendrils private void showWebBrowser() { // Telemetry for in-app browser usage. // Logging here because WebBrowserActivity is launched in a separate process. @@ -1267,7 +1284,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } - // TODO: Move this to KMEA during Keyman 13.0 refactoring + // TODO(v20): Move this to KMEA during Keyman 13.0 refactoring public static void useLocalKMP(Context context, Uri data) { if (data != null) { useLocalKMP(context, data, false); @@ -1410,6 +1427,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene }); } + // TODO(v20): cleanup setting draw item [sub]titles; lots of repetition and unnecessary conditions private void initializeDrawerItemSubtitles(NavigationView navigationView) { setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption, getString(R.string.drawer_subtitle_spacebar_caption)); @@ -1417,8 +1435,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getString(R.string.drawer_subtitle_show_osk)); setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report, getString(R.string.drawer_subtitle_send_crash_report)); - setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard, - getString(R.string.drawer_subtitle_about_current_keyboard)); updateThemeDrawerSubtitle(navigationView); updateCurrentKeyboardDrawerSubtitle(navigationView); } @@ -1632,6 +1648,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene } } + // TODO(v20): remove private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId, final Runnable onActivate) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); @@ -1671,6 +1688,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene }); } + // TODO(v20): remove private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) { MenuItem menuItem = navigationView.getMenu().findItem(menuItemId); if (menuItem == null) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 80701ad683..829daf755e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2887,6 +2887,9 @@ public final class KMManager { public static int getCurrentKeyboardIndex(Context context) { String key = KMKeyboard.currentKeyboard(); + if(key == null) { + return KeyboardController.INDEX_NOT_FOUND; + } return KeyboardController.getInstance().getKeyboardIndex(key); } @@ -2895,11 +2898,6 @@ public final class KMManager { if(index < 0) { // index can be undefined if user installs Keyman (without launching it) // and then enables Keyaman as a system keyboard from the Android settings menus. - // We'll only log if key isn't for fallback keyboard - String key = KMKeyboard.currentKeyboard(); - if (!isDefaultKey(key)) { - KMLog.LogError(TAG, "Failed getCurrentKeyboardIndex check for keyboard: " + key); - } return null; } return KeyboardController.getInstance().getKeyboardInfo(index); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/data/KeyboardController.java b/android/KMEA/app/src/main/java/com/keyman/engine/data/KeyboardController.java index 7ae2dfa2eb..1981979382 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/data/KeyboardController.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/data/KeyboardController.java @@ -188,16 +188,15 @@ public class KeyboardController { * If no match, returns INDEX_NOT_FOUND * @param key - String of the key to find * @return int - Index of the matching keyboard + * + * Note: do not perform logging here because KMLog.tagDebugInfo can call this, causing recursion */ public int getKeyboardIndex(String key) { - int index = INDEX_NOT_FOUND; if (!isInitialized || list == null) { - KMLog.LogError(TAG, "getKeyboardIndex while KeyboardController() not initialized"); - return index; + return INDEX_NOT_FOUND; } if (key == null || key.isEmpty()) { - KMLog.LogError(TAG, "getKeyboardIndex while key is null"); - return index; + return INDEX_NOT_FOUND; } synchronized (list) { @@ -209,11 +208,7 @@ public class KeyboardController { } } - // We'll only log if key isn't for fallback keyboard - if (!KMManager.isDefaultKey(key)) { - KMLog.LogError(TAG, "getKeyboardIndex failed for key " + key); - } - return index; + return INDEX_NOT_FOUND; } /** diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java index 633f5c853b..81d1bf70a6 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java @@ -21,6 +21,7 @@ import io.sentry.Breadcrumb; import io.sentry.Sentry; import io.sentry.SentryLevel; +// TODO(v20): make KMLog more resilient to re-entrancy by preventing nested calls with a sentinel flag public final class KMLog { private static final String TAG = "KMLog";