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); + } +}