mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
fix(android): show updates for lexical models
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
With this change the model info activity will show an available update to the lexical model. Commit partially drafted by AI (Gemini 3 Flash Preview). Fixes: #16226 Build-bot: release:android
This commit is contained in:
parent
2c480253b8
commit
a332c0d464
8 changed files with 229 additions and 38 deletions
|
|
@ -159,14 +159,19 @@ public final class LanguageSettingsActivity extends BaseActivity {
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
// Start ModelPickerActivity
|
||||
Intent intent = new Intent(context, ModelPickerActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KMManager.KMKey_LanguageID, lgCode);
|
||||
bundle.putString(KMManager.KMKey_LanguageName, lgName);
|
||||
bundle.putString(KMManager.KMKey_CustomHelpLink, customHelpLink);
|
||||
Intent i = new Intent(context, ModelPickerActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
i.putExtras(bundle);
|
||||
startActivity(i);
|
||||
HashMap<String, String> lmInfo = KMManager.getAssociatedLexicalModel(lgCode);
|
||||
if (lmInfo != null) {
|
||||
bundle.putSerializable(KMManager.KMKey_LexicalModel, lmInfo);
|
||||
} else {
|
||||
bundle.putString(KMManager.KMKey_LanguageID, lgCode);
|
||||
bundle.putString(KMManager.KMKey_LanguageName, lgName);
|
||||
bundle.putString(KMManager.KMKey_CustomHelpLink, customHelpLink);
|
||||
}
|
||||
intent.putExtras(bundle);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
return true;
|
||||
} else if (item.getItemId() == R.id.action_update_keyboards) {
|
||||
KMManager.getUpdateTool().executeOpenUpdates();
|
||||
// Dismiss icon
|
||||
// Dismiss icon - we intentionally hide the icon, even if the user cancels the update
|
||||
updateUpdateCountIndicator(0);
|
||||
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
|
||||
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
|
||||
|
|
|
|||
|
|
@ -756,6 +756,32 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array list with all lexical model hashmaps matching the lexical model ID.
|
||||
* @param context
|
||||
* @param lexicalModelId - ID of the lexical model
|
||||
* @return Array of lexical model hashmaps with lexicalModelId. Otherwise empty.
|
||||
*/
|
||||
public static ArrayList<HashMap<String, String>> getLexicalModelsMatchingId(Context context, String lexicalModelId) {
|
||||
ArrayList<HashMap<String, String>> lexicalModels = new ArrayList<>();
|
||||
|
||||
if (lexicalModelsList == null) {
|
||||
lexicalModelsList = getLexicalModelsList(context);
|
||||
}
|
||||
|
||||
if (lexicalModelsList != null) {
|
||||
int length = lexicalModelsList.size();
|
||||
for (int i=0; i < length; i++) {
|
||||
HashMap<String, String> lmInfo = lexicalModelsList.get(i);
|
||||
if (lexicalModelId.equals(lmInfo.get(KMManager.KMKey_LexicalModelID))) {
|
||||
lexicalModels.add(lmInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lexicalModels;
|
||||
}
|
||||
|
||||
public static HashMap<String, String> getLexicalModelInfo(Context context,int index) {
|
||||
if (index < 0) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import android.widget.Toast;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.os.BundleCompat;
|
||||
|
||||
import com.keyman.engine.cloud.CloudApiTypes;
|
||||
import com.keyman.engine.cloud.CloudDownloadMgr;
|
||||
|
|
@ -69,14 +70,15 @@ public final class ModelPickerActivity extends BaseActivity {
|
|||
TextView textView = (TextView) findViewById(R.id.bar_title);
|
||||
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
String newLanguageID = bundle.getString(KMManager.KMKey_LanguageID);
|
||||
String newCustomHelpLink = bundle.getString(KMManager.KMKey_CustomHelpLink, "");
|
||||
HashMap<String, String> lmInfo = BundleCompat.getSerializable(bundle, KMManager.KMKey_LexicalModel, HashMap.class);
|
||||
final String newLanguageID = lmInfo != null ? lmInfo.get(KMManager.KMKey_LanguageID) : bundle.getString(KMManager.KMKey_LanguageID, "");
|
||||
final String newCustomHelpLink = lmInfo != null ? lmInfo.get(KMManager.KMKey_CustomHelpLink) : bundle.getString(KMManager.KMKey_CustomHelpLink, "");
|
||||
final String languageName = lmInfo != null ? lmInfo.get(KMManager.KMKey_LanguageName) : bundle.getString(KMManager.KMKey_LanguageName, "");
|
||||
|
||||
// Sometimes we need to re-initialize the list of models that are displayed in the ListView
|
||||
languageID = newLanguageID;
|
||||
customHelpLink = newCustomHelpLink;
|
||||
|
||||
final String languageName = bundle.getString(KMManager.KMKey_LanguageName);
|
||||
textView.setText(String.format(getString(R.string.model_picker_header), languageName));
|
||||
|
||||
listView = (ListView) findViewById(R.id.listView);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.keyman.engine.KMManager;
|
|||
import com.keyman.engine.KeyboardPickerActivity;
|
||||
import com.keyman.engine.R;
|
||||
import com.keyman.engine.cloud.CloudApiTypes;
|
||||
import com.keyman.engine.data.CloudRepository;
|
||||
import com.keyman.engine.data.Keyboard;
|
||||
import com.keyman.engine.data.KeyboardController;
|
||||
import com.keyman.engine.data.LexicalModel;
|
||||
|
|
@ -188,7 +189,6 @@ public class CloudDataJsonUtil {
|
|||
}
|
||||
|
||||
public static void processLexicalModelPackageUpdateJSON(Context aContext, JSONObject pkgData, List<Bundle> updateBundles) {
|
||||
boolean saveModelsList = false;
|
||||
// Parse for lexical model package updates
|
||||
if (pkgData.has(CDKey_Models)) {
|
||||
try {
|
||||
|
|
@ -201,18 +201,11 @@ public class CloudDataJsonUtil {
|
|||
String cloudVersion = cloudModelObj.getString(CDKey_Version);
|
||||
String cloudKMP = cloudModelObj.getString(CDKey_KMP);
|
||||
// Valid lexical model package exists. See if lexical model list needs to be updated
|
||||
// Valid keyboard package exists. See if keyboard list needs to be updated
|
||||
int index = KeyboardPickerActivity.getLexicalModelIndex(aContext, lexicalModelID);
|
||||
if (index != -1) {
|
||||
HashMap<String, String> lmInfo = KeyboardPickerActivity.getLexicalModelInfo(aContext, index);
|
||||
String version = lmInfo.get(KMManager.KMKey_Version);
|
||||
ArrayList<HashMap<String, String>> lmInfos = KeyboardPickerActivity.getLexicalModelsMatchingId(aContext, lexicalModelID);
|
||||
for (HashMap<String, String> lmInfo : lmInfos) {
|
||||
String version = lmInfo.get(KMManager.KMKey_LexicalModelVersion);
|
||||
if (lexicalModelID.equalsIgnoreCase(lmInfo.get(KMManager.KMKey_LexicalModelID)) &&
|
||||
(FileUtils.compareVersions(cloudVersion, version) == FileUtils.VERSION_GREATER) &&
|
||||
(!MapCompat.getOrDefault(lmInfo, KMManager.KMKey_KMPLink, "").equalsIgnoreCase(cloudKMP))) {
|
||||
// Update keyboard with the latest KMP link
|
||||
lmInfo.put(KMManager.KMKey_KMPLink, cloudKMP);
|
||||
KeyboardPickerActivity.addLexicalModel(aContext, lmInfo);
|
||||
|
||||
(FileUtils.compareVersions(cloudVersion, version) == FileUtils.VERSION_GREATER)) {
|
||||
// Update bundle list
|
||||
LexicalModel lm = new LexicalModel(
|
||||
lmInfo.get(KMManager.KMKey_PackageID),
|
||||
|
|
@ -220,13 +213,29 @@ public class CloudDataJsonUtil {
|
|||
lmInfo.get(KMManager.KMKey_LexicalModelName),
|
||||
lmInfo.get(KMManager.KMKey_LanguageID),
|
||||
lmInfo.get(KMManager.KMKey_LanguageName),
|
||||
lmInfo.get(KMManager.KMKey_Version),
|
||||
lmInfo.get(KMManager.KMKey_LexicalModelVersion),
|
||||
lmInfo.get(KMManager.KMKey_CustomHelpLink),
|
||||
lmInfo.get(KMManager.KMKey_KMPLink));
|
||||
Bundle bundle = new Bundle(lm.buildDownloadBundle());
|
||||
updateBundles.add(bundle);
|
||||
String updateKMP = lm.getUpdateKMP();
|
||||
if (cloudLinkIsNewer(updateKMP, cloudKMP)) {
|
||||
// Update lexical model info with the latest KMP link after appending languageID
|
||||
String languageID = lm.getLanguageID();
|
||||
String link = String.format("%s&bcp47=%s", cloudKMP, languageID);
|
||||
lmInfo.put(KMManager.KMKey_KMPLink, link);
|
||||
KeyboardPickerActivity.addLexicalModel(aContext, lmInfo);
|
||||
lm.setUpdateKMP(link);
|
||||
updateKMP = link;
|
||||
LexicalModel cached = CloudRepository.shared.getLexicalModel(aContext, languageID, lexicalModelID);
|
||||
if (cached != null) {
|
||||
cached.setUpdateKMP(link);
|
||||
}
|
||||
}
|
||||
|
||||
saveModelsList = true;
|
||||
if (updateKMP != null && !updateKMP.isEmpty()) {
|
||||
// Update bundle list for update notifications
|
||||
Bundle bundle = new Bundle(lm.buildDownloadBundle());
|
||||
updateBundles.add(bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -367,14 +376,13 @@ public class CloudDataJsonUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (updateKMP == null || updateKMP.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
Uri cloudLink = Uri.parse(cloudKMP);
|
||||
|
||||
if (updateKMP == null || updateKMP.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Uri localLink = Uri.parse(updateKMP);
|
||||
Uri cloudLink = Uri.parse(cloudKMP);
|
||||
|
||||
boolean pathsMatch = localLink.getLastPathSegment().equalsIgnoreCase(cloudLink.getLastPathSegment());
|
||||
boolean cloudVersionNewer = FileUtils.compareVersions(
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ public class CloudCatalogDownloadCallback implements ICloudDownloadCallback<Data
|
|||
|
||||
// The actual update check
|
||||
CloudDataJsonUtil.processKeyboardPackageUpdateJSON(context, jsonTuple.packagesJSON, updateBundles);
|
||||
CloudDataJsonUtil.processLexicalModelPackageUpdateJSON(context, jsonTuple.packagesJSON, updateBundles);
|
||||
|
||||
// Only add installed kmp keyboards
|
||||
aDataSet.keyboards.clear();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.keyman.engine.cloud.CloudDownloadMgr;
|
|||
import com.keyman.engine.packages.JSONUtils;
|
||||
import com.keyman.engine.util.BCP47;
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
import com.keyman.engine.util.FileUtils;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.VersionUtils;
|
||||
|
||||
|
|
@ -158,10 +159,23 @@ public class CloudRepository {
|
|||
* @return LexicalModel of an associated lexical model. Null if no match found
|
||||
*/
|
||||
public LexicalModel getAssociatedLexicalModel(@NonNull Context context, String languageID) {
|
||||
return getLexicalModel(context, languageID, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lexical model with modelID associated with the given language ID. If
|
||||
* modelID is null, return the first model associated with languageID. Available
|
||||
* models are from the cloud catalog and locally installed models.
|
||||
* @param context Context
|
||||
* @param languageID String of the language ID to search
|
||||
* @param modelID String of the lexical model ID.
|
||||
* @return LexicalModel of an associated lexical model. Null if no match found.
|
||||
*/
|
||||
public LexicalModel getLexicalModel(@NonNull Context context, String languageID, String modelID) {
|
||||
if (memCachedDataset != null) {
|
||||
for (int i=0; i < memCachedDataset.lexicalModels.getCount(); i++) {
|
||||
LexicalModel lm = memCachedDataset.lexicalModels.getItem(i);
|
||||
if (BCP47.languageEquals(lm.getLanguageID(), languageID)) {
|
||||
if (BCP47.languageEquals(lm.getLanguageID(), languageID) && (modelID == null || modelID.equals(lm.getLexicalModelID()))) {
|
||||
return lm;
|
||||
}
|
||||
}
|
||||
|
|
@ -253,6 +267,41 @@ public class CloudRepository {
|
|||
downloadMetaDataFromServer(context,updateHandler,onSuccess,onFailure);
|
||||
}
|
||||
|
||||
private void mergeLexicalModels(Dataset.LexicalModels datasetModels, List<LexicalModel> newModels) {
|
||||
if (newModels == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<LexicalModel> existingModels = new ArrayList<>(datasetModels.asList());
|
||||
List<LexicalModel> mergedModels = new ArrayList<>();
|
||||
|
||||
// Process all incoming models
|
||||
for (LexicalModel newModel : newModels) {
|
||||
LexicalModel existingMatch = null;
|
||||
for (int i = 0; i < existingModels.size(); i++) {
|
||||
if (newModel.equals(existingModels.get(i))) {
|
||||
existingMatch = existingModels.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingMatch != null) {
|
||||
if (FileUtils.compareVersions(existingMatch.getVersion(), newModel.getVersion()) != FileUtils.VERSION_LOWER) {
|
||||
mergedModels.add(existingMatch);
|
||||
} else {
|
||||
mergedModels.add(newModel);
|
||||
}
|
||||
} else {
|
||||
mergedModels.add(newModel);
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining existing models that weren't matched
|
||||
mergedModels.addAll(existingModels);
|
||||
|
||||
datasetModels.clear();
|
||||
datasetModels.addAll(mergedModels);
|
||||
}
|
||||
|
||||
/**
|
||||
* precache dataset and notify callbacks if no update from cloud api services is necessary.
|
||||
|
|
@ -286,11 +335,13 @@ public class CloudRepository {
|
|||
languageCodes.add(installedSet.getItem(i).code);
|
||||
}
|
||||
|
||||
// add all models from installed set
|
||||
memCachedDataset.lexicalModels.addAll(installedSet.lexicalModels.asList());
|
||||
|
||||
// Get kmp.json info from installed (adhoc and cloud) models.
|
||||
// Consolidate kmp.json info from packages/
|
||||
JSONObject kmpLanguagesArray = wrapKmpKeyboardJSON(JSONUtils.getLanguages());
|
||||
JSONArray kmpLexicalModelsArray = JSONUtils.getLexicalModels();
|
||||
final boolean fromKMP = true;
|
||||
|
||||
try {
|
||||
if (kmpLanguagesArray.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Languages).
|
||||
|
|
@ -298,7 +349,7 @@ public class CloudRepository {
|
|||
memCachedDataset.keyboards.addAll(CloudDataJsonUtil.processKeyboardJSON(kmpLanguagesArray, true));
|
||||
}
|
||||
if (kmpLexicalModelsArray.length() > 0) {
|
||||
memCachedDataset.lexicalModels.addAll(CloudDataJsonUtil.processLexicalModelJSON(kmpLexicalModelsArray, fromKMP));
|
||||
mergeLexicalModels(memCachedDataset.lexicalModels, CloudDataJsonUtil.processLexicalModelJSON(kmpLexicalModelsArray, true));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
KMLog.LogException(TAG, "preCacheDataSet error ", e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
package com.keyman.engine.data;
|
||||
|
||||
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 java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CloudRepositoryTests {
|
||||
|
||||
private CloudRepository repository;
|
||||
private Dataset dataset;
|
||||
private Context context;
|
||||
|
||||
private boolean containsModel(Dataset.LexicalModels models, String modelID) {
|
||||
for (int i = 0; i < models.getCount(); i++) {
|
||||
if (models.getItem(i).getLexicalModelID().equals(modelID)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
repository = CloudRepository.shared;
|
||||
dataset = new Dataset(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeLexicalModels_PreservesUniqueExistingModels() throws Exception {
|
||||
// Setup
|
||||
dataset.lexicalModels.add(new LexicalModel("pkg1", "model1", "Model 1", "en", "English", "1.0", "", ""));
|
||||
List<LexicalModel> newModels = new ArrayList<>();
|
||||
newModels.add(new LexicalModel("pkg2", "model2", "Model 2", "fr", "French", "1.0", "", ""));
|
||||
|
||||
// Execute
|
||||
Method mergeMethod = CloudRepository.class.getDeclaredMethod("mergeLexicalModels", Dataset.LexicalModels.class, List.class);
|
||||
mergeMethod.setAccessible(true);
|
||||
mergeMethod.invoke(repository, dataset.lexicalModels, newModels);
|
||||
|
||||
// Verify
|
||||
Assert.assertEquals(2, dataset.lexicalModels.getCount());
|
||||
Assert.assertTrue(containsModel(dataset.lexicalModels, "model1"));
|
||||
Assert.assertTrue(containsModel(dataset.lexicalModels, "model2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeLexicalModels_UpdatesVersion() throws Exception {
|
||||
// Setup
|
||||
dataset.lexicalModels.add(new LexicalModel("pkg1", "model1", "Model 1", "en", "English", "1.0", "", ""));
|
||||
|
||||
List<LexicalModel> newModels = new ArrayList<>();
|
||||
// New model (same ID, newer version v1.1)
|
||||
newModels.add(new LexicalModel("pkg1", "model1", "Model 1", "en", "English", "1.1", "", ""));
|
||||
|
||||
// Execute
|
||||
Method mergeMethod = CloudRepository.class.getDeclaredMethod("mergeLexicalModels", Dataset.LexicalModels.class, List.class);
|
||||
mergeMethod.setAccessible(true);
|
||||
mergeMethod.invoke(repository, dataset.lexicalModels, newModels);
|
||||
|
||||
// Verify
|
||||
Assert.assertEquals(1, dataset.lexicalModels.getCount());
|
||||
LexicalModel result = dataset.lexicalModels.getItem(0);
|
||||
Assert.assertEquals("1.1", result.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeLexicalModels_KeepsOlderIfNewerIsLowerVersion() throws Exception {
|
||||
// Setup
|
||||
dataset.lexicalModels.add(new LexicalModel("pkg1", "model1", "Model 1", "en", "English", "1.2", "", ""));
|
||||
|
||||
List<LexicalModel> newModels = new ArrayList<>();
|
||||
// New model (same ID, older version v1.1)
|
||||
newModels.add(new LexicalModel("pkg1", "model1", "Model 1", "en", "English", "1.1", "", ""));
|
||||
|
||||
// Execute
|
||||
Method mergeMethod = CloudRepository.class.getDeclaredMethod("mergeLexicalModels", Dataset.LexicalModels.class, List.class);
|
||||
mergeMethod.setAccessible(true);
|
||||
mergeMethod.invoke(repository, dataset.lexicalModels, newModels);
|
||||
|
||||
// Verify
|
||||
Assert.assertEquals(1, dataset.lexicalModels.getCount());
|
||||
LexicalModel result = dataset.lexicalModels.getItem(0);
|
||||
Assert.assertEquals("1.2", result.getVersion());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue