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 8d30984bde..7cff5b5e80 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
@@ -24,7 +24,7 @@ import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.util.KMPLink;
import com.tavultesoft.kmea.util.KMString;
-import com.tavultesoft.kmea.util.WebViewUtil;
+import com.tavultesoft.kmea.util.WebViewUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -166,7 +166,7 @@ public class KMPBrowserActivity extends BaseActivity {
@Override
protected void onDestroy() {
super.onDestroy();
- WebViewUtil.cleanup(webView);
+ WebViewUtils.cleanup(webView);
}
@Override
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 04c1eec498..917a2c27e0 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
@@ -40,6 +40,8 @@ import com.keyman.android.DownloadIntentService;
import com.tavultesoft.kmea.util.KMLog;
import com.tavultesoft.kmea.util.KMPLink;
import com.tavultesoft.kmea.util.KMString;
+import com.tavultesoft.kmea.util.WebViewUtils;
+import com.tavultesoft.kmea.util.WebViewUtils.EngineModeType;
import android.Manifest;
import android.app.ProgressDialog;
@@ -197,6 +199,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
textView.setTextSize((float) textSize);
textView.setSelection(textView.getText().length());
+ checkChromeVersion();
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
checkGetStarted();
}
@@ -764,6 +767,24 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.setMaySendCrashReport(maySendCrashReport);
}
+ private void checkChromeVersion() {
+ if (WebViewUtils.getEngineModeType(context,"") != EngineModeType.ENGINE_MODE_TYPE_FULL) {
+ // Notify minimum Chrome version needed
+ String message = "Minimum Chrome version 57 needed for Keyman.";
+ //KMLog.LogError(TAG, message);
+ Toast.makeText(getApplicationContext(), message,
+ Toast.LENGTH_LONG).show();
+
+ // Launch PlayStore to update Chrome
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.android.chrome"));
+ startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/apps/details?id=com.android.chome")));
+ }
+ }
+ }
+
private Uri requestPermissionIntentUri;
@Override
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebViewFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebViewFragment.java
index 8ed91584cf..d844a06996 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebViewFragment.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebViewFragment.java
@@ -14,7 +14,7 @@ import com.stepstone.stepper.StepperLayout;
import com.stepstone.stepper.VerificationError;
import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.util.FileUtils;
-import com.tavultesoft.kmea.util.WebViewUtil;
+import com.tavultesoft.kmea.util.WebViewUtils;
import android.graphics.Bitmap;
import android.os.Bundle;
@@ -137,7 +137,7 @@ public class WebViewFragment extends Fragment implements BlockingStep {
@Override
public void onPageFinished(WebView view, String url) {
// Inject a meta viewport tag into the head of the file if it doesn't exist
- WebViewUtil.injectViewport(view);
+ WebViewUtils.injectViewport(view);
}
});
diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml
index 2dcde01ccd..42e5af4782 100644
--- a/android/KMEA/app/src/main/AndroidManifest.xml
+++ b/android/KMEA/app/src/main/AndroidManifest.xml
@@ -4,6 +4,10 @@
+
+
+
+
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHelpFileActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHelpFileActivity.java
index fc0e5ddd17..d74decb7b7 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHelpFileActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHelpFileActivity.java
@@ -31,7 +31,7 @@ import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.util.FileProviderUtils;
import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.HelpFile;
-import com.tavultesoft.kmea.util.WebViewUtil;
+import com.tavultesoft.kmea.util.WebViewUtils;
import java.io.File;
@@ -144,7 +144,7 @@ public class KMHelpFileActivity extends BaseActivity {
@Override
public void onPageFinished(WebView view, String url) {
// Inject a meta viewport tag into the head of the file if it doesn't exist
- WebViewUtil.injectViewport(view);
+ WebViewUtils.injectViewport(view);
}
});
@@ -167,7 +167,7 @@ public class KMHelpFileActivity extends BaseActivity {
@Override
protected void onDestroy() {
super.onDestroy();
- WebViewUtil.cleanup(webView);
+ WebViewUtils.cleanup(webView);
}
@Override
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
index 2273c96162..9e5c7bcefc 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
@@ -75,6 +75,7 @@ import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.KMLog;
import com.tavultesoft.kmea.util.KMString;
import com.tavultesoft.kmea.util.MapCompat;
+import com.tavultesoft.kmea.util.WebViewUtils;
import org.json.JSONArray;
import org.json.JSONException;
@@ -367,6 +368,15 @@ public final class KMManager {
return appVersion;
}
+ /**
+ * Get the Keyman Engine mode based on the Chrome version
+ * @param aContext
+ * @return WebViewUtils.EngineModeType
+ */
+ public static WebViewUtils.EngineModeType getEngineMode(Context aContext) {
+ return WebViewUtils.getEngineModeType(aContext, "");
+ }
+
// Check if a keyboard namespace is reserved
public static boolean isReservedNamespace(String packageID) {
if (packageID.equals(KMDefault_UndefinedPackageID)) {
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtil.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtil.java
deleted file mode 100644
index 43a67083c8..0000000000
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtil.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Copyright (C) 2021 SIL International. All rights reserved.
- */
-package com.tavultesoft.kmea.util;
-
-import android.view.ViewGroup;
-import android.webkit.WebView;
-
-public final class WebViewUtil {
- // Inject a meta viewport tag into the head of the file if it doesn't exist
- public static void injectViewport(WebView webView) {
- if (webView != null) {
- webView.loadUrl(
- "javascript:(function() {" +
- "if(document.head && !document.querySelectorAll('meta[name=viewport]').length) {"+
- "let meta=document.createElement('meta');"+
- "meta.name='viewport';"+
- "meta.content='width=device-width, initial-scale=1';"+
- "document.head.appendChild(meta);"+
- "}"+
- "})()"
- );
- }
- }
-
- // Blank the webView and destroy completely
- // Reference: https://stackoverflow.com/questions/17418503/destroy-webview-in-android/17458577#17458577
- public static void cleanup(WebView webView) {
- if (webView != null) {
- webView.loadUrl("about:blank");
- ViewGroup viewGroup = (ViewGroup) webView.getParent();
- if (viewGroup != null) {
- viewGroup.removeView(webView);
- }
- webView.removeAllViews();
- webView.destroy();
- webView = null;
- }
-
- }
-}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtils.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtils.java
new file mode 100644
index 0000000000..05f0d0d28e
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/WebViewUtils.java
@@ -0,0 +1,98 @@
+/**
+ * Keyman is copyright (C) SIL International. MIT License.
+ */
+package com.tavultesoft.kmea.util;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import com.tavultesoft.kmea.util.KMLog;
+
+public final class WebViewUtils {
+ public static final String TAG = "WebViewUtils";
+
+ // Keyman Engine functionality based on Chrome version
+ public enum EngineModeType {
+ ENGINE_MODE_TYPE_DISABLED, // WebView doesn't support touch keyboard features
+ ENGINE_MODE_TYPE_DEGRADED, // WebView supports touch keyboards but not LDML keyboards
+ ENGINE_MODE_TYPE_FULL; // WebView supports touch keyboards and LDML keyboards
+ }
+
+ /**
+ * Get the Keyman Engine mode based on the Chrome version.
+ * @param context - The context
+ * @param chromeVersion - String of the device's Chrome version.
+ * If not provided, the device's Chrome version is queried
+ * @return EngineModeType
+ */
+ public static EngineModeType getEngineModeType(Context context, String chromeVersion) {
+ if (context == null) {
+ return EngineModeType.ENGINE_MODE_TYPE_DISABLED;
+ }
+ if (chromeVersion == null || chromeVersion.isEmpty()) {
+ chromeVersion = getChromeVersion(context);
+ }
+
+ if (FileUtils.compareVersions(chromeVersion, "57.0") == FileUtils.VERSION_GREATER) {
+ return EngineModeType.ENGINE_MODE_TYPE_FULL;
+ } else if (FileUtils.compareVersions(chromeVersion, "37.0") == FileUtils.VERSION_GREATER) {
+ return EngineModeType.ENGINE_MODE_TYPE_DEGRADED;
+ }
+
+ return EngineModeType.ENGINE_MODE_TYPE_DISABLED;
+ }
+
+ // Inject a meta viewport tag into the head of the file if it doesn't exist
+ public static void injectViewport(WebView webView) {
+ if (webView != null) {
+ webView.loadUrl(
+ "javascript:(function() {" +
+ "if(document.head && !document.querySelectorAll('meta[name=viewport]').length) {"+
+ "let meta=document.createElement('meta');"+
+ "meta.name='viewport';"+
+ "meta.content='width=device-width, initial-scale=1';"+
+ "document.head.appendChild(meta);"+
+ "}"+
+ "})()"
+ );
+ }
+ }
+
+ // Blank the webView and destroy completely
+ // Reference: https://stackoverflow.com/questions/17418503/destroy-webview-in-android/17458577#17458577
+ public static void cleanup(WebView webView) {
+ if (webView != null) {
+ webView.loadUrl("about:blank");
+ ViewGroup viewGroup = (ViewGroup) webView.getParent();
+ if (viewGroup != null) {
+ viewGroup.removeView(webView);
+ }
+ webView.removeAllViews();
+ webView.destroy();
+ webView = null;
+ }
+ }
+
+ /**
+ * Get the Chrome version. Returns empty string if Chrome not installed.
+ * @param context - The context
+ * @return String
+ */
+ private static String getChromeVersion(Context context) {
+ if (context == null) {
+ return "";
+ }
+ try {
+ PackageInfo pInfo;
+ pInfo = context.getPackageManager().getPackageInfo("com.android.chrome", PackageManager.GET_ACTIVITIES);
+ if (pInfo != null) {
+ return pInfo.versionName;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ KMLog.LogInfo(TAG, "Chrome not installed");
+ }
+ return "";
+ }
+}
diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/WebViewUtilsTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/WebViewUtilsTest.java
new file mode 100644
index 0000000000..4c079d35d3
--- /dev/null
+++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/WebViewUtilsTest.java
@@ -0,0 +1,53 @@
+/**
+ * Keyman is copyright (C) SIL International. MIT License.
+ */
+package com.tavultesoft.kmea.util;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import com.tavultesoft.kmea.util.WebViewUtils;
+
+@RunWith(RobolectricTestRunner.class)
+public class WebViewUtilsTest {
+ private Context context = null;
+
+ @Before
+ public void getContext() {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void test_ChromeEmpty_EngineModeTypeDisabled() {
+ String chromeVersion = "";
+ Assert.assertEquals(WebViewUtils.getEngineModeType(context, chromeVersion),
+ WebViewUtils.EngineModeType.ENGINE_MODE_TYPE_DISABLED);
+ }
+
+ @Test
+ public void test_Chrome36_EngineModeTypeDisabled() {
+ String chromeVersion = "36.0";
+ Assert.assertEquals(WebViewUtils.getEngineModeType(context, chromeVersion),
+ WebViewUtils.EngineModeType.ENGINE_MODE_TYPE_DISABLED);
+ }
+
+ @Test
+ public void test_Chrome57_EngineModeTypeDegraded() {
+ String chromeVersion = "57.0";
+ Assert.assertEquals(WebViewUtils.getEngineModeType(context, chromeVersion),
+ WebViewUtils.EngineModeType.ENGINE_MODE_TYPE_DEGRADED);
+ }
+
+ @Test
+ public void test_Chrome58_EngineModeTypeFull() {
+ String chromeVersion = "58.0";
+ Assert.assertEquals(WebViewUtils.getEngineModeType(context, chromeVersion),
+ WebViewUtils.EngineModeType.ENGINE_MODE_TYPE_FULL);
+ }
+}