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 2ff31baffd..b7b34c7f64 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 @@ -24,6 +24,7 @@ import com.tavultesoft.kmea.KMManager.KeyboardType; import com.tavultesoft.kmea.KMTextView; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; +import com.tavultesoft.kmea.cloud.CloudApiTypes; import com.tavultesoft.kmea.packages.PackageProcessor; import com.tavultesoft.kmea.util.FileUtils; import com.tavultesoft.kmea.util.FileProviderUtils; @@ -90,6 +91,8 @@ import android.widget.Toast; import io.sentry.android.core.SentryAndroid; import io.sentry.core.Sentry; +import static com.tavultesoft.kmea.KMKeyboardDownloaderActivity.kKeymanApiModelURL; + public class MainActivity extends AppCompatActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, ActivityCompat.OnRequestPermissionsResultCallback { public static Context context; @@ -145,6 +148,21 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi Intent packageIntent = new Intent(getApplicationContext(), PackageActivity.class); packageIntent.putExtras(bundle); startActivity(packageIntent); + + // Determine if associated lexical model should be downloaded + if (FileUtils.hasKeymanPackageExtension(kmpFilename) && !FileUtils.hasLexicalModelPackageExtension(kmpFilename) + && (languageID != null) && !languageID.isEmpty()) { + String keyboardID = kmpFilename.substring(0, kmpFilename.lastIndexOf(FileUtils.KEYMANPACKAGE)); + ArrayList cloudQueries = new ArrayList<>(); + String _remoteLexicalModelUrl = String.format("%s?q=bcp47:%s", kKeymanApiModelURL, languageID); + cloudQueries.add(new CloudApiTypes.CloudApiParam( + CloudApiTypes.ApiTarget.KeyboardLexicalModels, _remoteLexicalModelUrl) + .setType(CloudApiTypes.JSONType.Array)); + // Keyboard package already downloaded, so this will just download associated lexical model. + // Can't call downloadLexicalModel() because it needs to already know the model ID + KMKeyboardDownloaderActivity.downloadKeyboard(context, languageID, keyboardID, cloudQueries); + } + break; } super.onReceiveResult(resultCode, resultData); @@ -331,14 +349,18 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi downloadKMP(scheme); break; case "keyman" : - // Convert opaque URI to hierarchical URI so the query parameters can be parsed - Builder builder = new Uri.Builder(); - builder.scheme("https") - .authority("keyman.com") - .appendPath("keyboards") - .encodedQuery(data.getEncodedQuery()); - data = Uri.parse(builder.build().toString()); - downloadKMP(scheme); + if (FileUtils.isKeymanLink(data.toString())) { + // Convert opaque URI to hierarchical URI so the query parameters can be parsed + Builder builder = new Uri.Builder(); + builder.scheme("https") + .authority("keyman.com") + .appendPath("keyboards") + .encodedQuery(data.getEncodedQuery()); + data = Uri.parse(builder.build().toString()); + downloadKMP(scheme); + } else { + Log.e(TAG, "Unrecognized scheme: " + scheme); + } break; default : Log.e(TAG, "Unrecognized scheme: " + scheme); diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml index f519253f24..91b75bf5ac 100644 --- a/android/KMEA/app/src/main/AndroidManifest.xml +++ b/android/KMEA/app/src/main/AndroidManifest.xml @@ -12,6 +12,11 @@ + + kbInfo: keyboardsList) { - String customKeyboard = MapCompat.getOrDefault(kbInfo, KMManager.KMKey_CustomKeyboard, "N"); - if (customKeyboard.equalsIgnoreCase("Y")) { + String pkgID = MapCompat.getOrDefault(kbInfo, KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID); + if (!pkgID.equalsIgnoreCase(KMManager.KMDefault_UndefinedPackageID)) { return true; } } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java index 6848b09a32..e8bcc56df7 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java @@ -238,13 +238,18 @@ public final class LanguageSettingsActivity extends AppCompatActivity { addButton = (ImageButton) findViewById(R.id.add_button); addButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - // Check that available keyboard information can be obtained via: - // 1. connection to cloud catalog - // 2. cached file - // 3. local kmp.json files in packages/ - if (KMManager.hasConnection(context) || CloudRepository.shared.hasCache(context) || - KeyboardPickerActivity.hasKeyboardFromPackage()){ - // Rework to use languuage-specific (KeyboardList) picker! + // Check scenarios to add available keyboards: + if (KMManager.hasConnection(context)){ + // Scenario 1: Connection to keyman.com catalog + // Pass the BCP47 language code to the KMPBrowserActivity + Intent i = new Intent(context, KMPBrowserActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + i.putExtra("languageCode", lgCode); + i.putExtra("languageName", lgName); + context.startActivity(i); + } else if (KeyboardPickerActivity.hasKeyboardFromPackage()) { + // Scenario 2: Local kmp.json files in packages/ Intent i = new Intent(context, KeyboardListActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); i.putExtra("languageCode", lgCode); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java index fdc8edf75a..4a34558115 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguagesSettingsActivity.java @@ -109,12 +109,15 @@ public final class LanguagesSettingsActivity extends AppCompatActivity { addButton = (ImageButton) findViewById(R.id.add_button); addButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - // Check that available keyboard information can be obtained via: - // 1. connection to cloud catalog - // 2. cached file - // 3. local kmp.json files in packages/ - if (KMManager.hasConnection(context) || CloudDataJsonUtil.getKeyboardCacheFile(context).exists() || - KeyboardPickerActivity.hasKeyboardFromPackage()){ + // Check scenarios to add available keyboards: + if (KMManager.hasConnection(context)) { + // Scenario 1: Connection to keyman.com catalog + Intent i = new Intent(context, KMPBrowserActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + context.startActivity(i); + } else if (KeyboardPickerActivity.hasKeyboardFromPackage()) { + // Scenario 2: Local kmp.json files in packages/ dismissOnSelect = false; Intent i = new Intent(context, LanguageListActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/JSONUtils.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/JSONUtils.java index 414f7b36db..a233269d4c 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/JSONUtils.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/JSONUtils.java @@ -139,7 +139,7 @@ public class JSONUtils { try { for (int i=0; i < a.length(); i++) { JSONObject o = a.getJSONObject(i); - if (o.getString("id").equals(id)) { + if (o.getString("id").toLowerCase().equals(id.toLowerCase())) { return i; } } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java index acfe437351..69c31be4ea 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java @@ -239,14 +239,47 @@ public final class FileUtils { if (u == null) { return ret; } + String lowerU = u.toLowerCase(); Pattern pattern = Pattern.compile("^http(s)?://(.+\\.)?keyman.com/.*"); - Matcher matcher = pattern.matcher(u); + Matcher matcher = pattern.matcher(lowerU); if (matcher.matches()) { ret = false; } return ret; } + /** + * Utility to parse a URL and determine if it's a valid keyman: + * Currently, only "keyman" scheme with "download" path and query is supported. + * Legacy keyman:// protocol is deprecated and not supported. + * @param u String of the URL + * @return boolean true if URL is a supported Keyman link + */ + public static boolean isKeymanLink(String u) { + boolean ret = false; + if (u == null) { + return ret; + } + String lowerU = u.toLowerCase(); + Pattern pattern = Pattern.compile("^keyman:(\\w+)\\?(.+)"); + Matcher matcher = pattern.matcher(lowerU); + // Check URL starts with "keyman" + if (matcher.matches() && (matcher.group(1) != null)) { + // For now, only handle "download" + switch (matcher.group(1).toLowerCase()) { + case "download": + if (matcher.group(2) != null) { + // Contains query + ret = true; + } + break; + default: + ret = false; + } + } + return ret; + } + /** * Utility to compare two version strings * @param v1 String diff --git a/android/KMEA/app/src/main/res/layout/activity_kmp_browser.xml b/android/KMEA/app/src/main/res/layout/activity_kmp_browser.xml new file mode 100644 index 0000000000..3dd6e2f424 --- /dev/null +++ b/android/KMEA/app/src/main/res/layout/activity_kmp_browser.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/JSONUtilsTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/JSONUtilsTest.java index 0918911670..4a425d9ba1 100644 --- a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/JSONUtilsTest.java +++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/JSONUtilsTest.java @@ -69,6 +69,7 @@ public class JSONUtilsTest { // Verify first and last language for sil_cameroon_qwerty Assert.assertEquals(0, JSONUtils.findID(languagesArray, "aal-Latn")); + Assert.assertEquals(0, JSONUtils.findID(languagesArray, "aal-latn")); Assert.assertEquals(EXPECTED_NUM_LANGUAGES-1, JSONUtils.findID(languagesArray, "zuy-Latn")); } catch (JSONException e) { diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java index cec2c9819e..cb5f97590e 100644 --- a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java +++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java @@ -157,7 +157,7 @@ public class PackageProcessorTest { Assert.assertEquals(amharic, keyboards[0]); Assert.assertEquals(TEST_GFF_KBD_COUNT, keyboards.length); - languageID = "gez"; + languageID = "GEZ"; keyboards = PP.processEntry(json.getJSONArray("keyboards").getJSONObject(0), "gff_amh_7_test_json", pkgVersion, languageID); HashMap geez = new HashMap(); diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java index f652b3d2cb..b1f4663a00 100644 --- a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java +++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java @@ -53,6 +53,31 @@ public class FileUtilsTest { Assert.assertTrue(FileUtils.isCustomKeyboard("https://amerikeyman.com/")); } + @Test + public void test_isKeymanLink() { + Assert.assertFalse(FileUtils.isKeymanLink(null)); + Assert.assertFalse(FileUtils.isKeymanLink("")); + + // Valid Keyman links + Assert.assertTrue(FileUtils.isKeymanLink("keyman:download?keyboard")); + Assert.assertTrue(FileUtils.isKeymanLink("Keyman:Download?keyboard")); + + // keyman:// invalid + Assert.assertFalse(FileUtils.isKeymanLink("keyman://keyboard")); + Assert.assertFalse(FileUtils.isKeymanLink("Keyman://keyboard")); + Assert.assertFalse(FileUtils.isKeymanLink("keyman:download//keyboard")); + Assert.assertFalse(FileUtils.isKeymanLink("keyman://download/keyboard")); + + // link missing query + Assert.assertFalse(FileUtils.isKeymanLink("keyman:download?")); + + // Other methods not supported + Assert.assertFalse(FileUtils.isKeymanLink("keyman:method//keyboard")); + Assert.assertFalse(FileUtils.isKeymanLink("keyman:method?keyboard")); + + Assert.assertFalse(FileUtils.isKeymanLink("example:keyman?")); + } + @Test public void test_compareVersions() { Assert.assertEquals(FileUtils.VERSION_INVALID, FileUtils.compareVersions(null, "1.0"));