aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/latin/personalization
diff options
context:
space:
mode:
authorAmin Bandali <bandali@kelar.org>2024-12-16 21:45:41 -0500
committerAmin Bandali <bandali@kelar.org>2025-01-11 14:17:35 -0500
commite9a0e66716dab4dd3184d009d8920de1961efdfa (patch)
tree02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/org/kelar/inputmethod/latin/personalization
parentfb3b9360d70596d7e921de8bf7d3ca99564a077e (diff)
downloadlatinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/personalization')
-rw-r--r--java/src/org/kelar/inputmethod/latin/personalization/AccountUtils.java66
-rw-r--r--java/src/org/kelar/inputmethod/latin/personalization/PersonalizationHelper.java108
-rw-r--r--java/src/org/kelar/inputmethod/latin/personalization/UserHistoryDictionary.java135
3 files changed, 309 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/personalization/AccountUtils.java b/java/src/org/kelar/inputmethod/latin/personalization/AccountUtils.java
new file mode 100644
index 000000000..45e551291
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/personalization/AccountUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.latin.personalization;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.util.Patterns;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class AccountUtils {
+ private AccountUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static Account[] getAccounts(final Context context) {
+ return AccountManager.get(context).getAccounts();
+ }
+
+ public static List<String> getDeviceAccountsEmailAddresses(final Context context) {
+ final ArrayList<String> retval = new ArrayList<>();
+ for (final Account account : getAccounts(context)) {
+ final String name = account.name;
+ if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
+ retval.add(name);
+ retval.add(name.split("@")[0]);
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Get all device accounts having specified domain name.
+ * @param context application context
+ * @param domain domain name used for filtering
+ * @return List of account names that contain the specified domain name
+ */
+ public static List<String> getDeviceAccountsWithDomain(
+ final Context context, final String domain) {
+ final ArrayList<String> retval = new ArrayList<>();
+ final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
+ for (final Account account : getAccounts(context)) {
+ if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
+ retval.add(account.name);
+ }
+ }
+ return retval;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/org/kelar/inputmethod/latin/personalization/PersonalizationHelper.java
new file mode 100644
index 000000000..7be7d1c8f
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.latin.personalization;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.kelar.inputmethod.latin.common.FileUtils;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.lang.ref.SoftReference;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Helps handle and manage personalized dictionaries such as {@link UserHistoryDictionary}.
+ */
+public class PersonalizationHelper {
+ private static final String TAG = PersonalizationHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
+ sLangUserHistoryDictCache = new ConcurrentHashMap<>();
+
+ @Nonnull
+ public static UserHistoryDictionary getUserHistoryDictionary(
+ final Context context, final Locale locale, @Nullable final String accountName) {
+ String lookupStr = locale.toString();
+ if (accountName != null) {
+ lookupStr += "." + accountName;
+ }
+ synchronized (sLangUserHistoryDictCache) {
+ if (sLangUserHistoryDictCache.containsKey(lookupStr)) {
+ final SoftReference<UserHistoryDictionary> ref =
+ sLangUserHistoryDictCache.get(lookupStr);
+ final UserHistoryDictionary dict = ref == null ? null : ref.get();
+ if (dict != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Use cached UserHistoryDictionary with lookup: " + lookupStr);
+ }
+ dict.reloadDictionaryIfRequired();
+ return dict;
+ }
+ }
+ final UserHistoryDictionary dict = new UserHistoryDictionary(
+ context, locale, accountName);
+ sLangUserHistoryDictCache.put(lookupStr, new SoftReference<>(dict));
+ return dict;
+ }
+ }
+
+ public static void removeAllUserHistoryDictionaries(final Context context) {
+ synchronized (sLangUserHistoryDictCache) {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry
+ : sLangUserHistoryDictCache.entrySet()) {
+ if (entry.getValue() != null) {
+ final UserHistoryDictionary dict = entry.getValue().get();
+ if (dict != null) {
+ dict.clear();
+ }
+ }
+ }
+ sLangUserHistoryDictCache.clear();
+ final File filesDir = context.getFilesDir();
+ if (filesDir == null) {
+ Log.e(TAG, "context.getFilesDir() returned null.");
+ return;
+ }
+ final boolean filesDeleted = FileUtils.deleteFilteredFiles(
+ filesDir, new DictFilter(UserHistoryDictionary.NAME));
+ if (!filesDeleted) {
+ Log.e(TAG, "Cannot remove dictionary files. filesDir: " + filesDir.getAbsolutePath()
+ + ", dictNamePrefix: " + UserHistoryDictionary.NAME);
+ }
+ }
+ }
+
+ private static class DictFilter implements FilenameFilter {
+ private final String mName;
+
+ DictFilter(final String name) {
+ mName = name;
+ }
+
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return name.startsWith(mName);
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/org/kelar/inputmethod/latin/personalization/UserHistoryDictionary.java
new file mode 100644
index 000000000..bbd96c61e
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.latin.personalization;
+
+import android.content.Context;
+
+import org.kelar.inputmethod.annotations.ExternallyReferenced;
+import org.kelar.inputmethod.annotations.UsedForTesting;
+import org.kelar.inputmethod.latin.BinaryDictionary;
+import org.kelar.inputmethod.latin.Dictionary;
+import org.kelar.inputmethod.latin.ExpandableBinaryDictionary;
+import org.kelar.inputmethod.latin.NgramContext;
+import org.kelar.inputmethod.latin.define.ProductionFlags;
+import org.kelar.inputmethod.latin.makedict.DictionaryHeader;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Locally gathers statistics about the words user types and various other signals like
+ * auto-correction cancellation or manual picks. This allows the keyboard to adapt to the
+ * typist over time.
+ */
+public class UserHistoryDictionary extends ExpandableBinaryDictionary {
+ static final String NAME = UserHistoryDictionary.class.getSimpleName();
+
+ // TODO: Make this constructor private
+ UserHistoryDictionary(final Context context, final Locale locale,
+ @Nullable final String account) {
+ super(context, getUserHistoryDictName(NAME, locale, null /* dictFile */, account), locale, Dictionary.TYPE_USER_HISTORY, null);
+ if (mLocale != null && mLocale.toString().length() > 1) {
+ reloadDictionaryIfRequired();
+ }
+ }
+
+ /**
+ * @returns the name of the {@link UserHistoryDictionary}.
+ */
+ @UsedForTesting
+ static String getUserHistoryDictName(final String name, final Locale locale,
+ @Nullable final File dictFile, @Nullable final String account) {
+ if (!ProductionFlags.ENABLE_PER_ACCOUNT_USER_HISTORY_DICTIONARY) {
+ return getDictName(name, locale, dictFile);
+ }
+ return getUserHistoryDictNamePerAccount(name, locale, dictFile, account);
+ }
+
+ /**
+ * Uses the currently signed in account to determine the dictionary name.
+ */
+ private static String getUserHistoryDictNamePerAccount(final String name, final Locale locale,
+ @Nullable final File dictFile, @Nullable final String account) {
+ if (dictFile != null) {
+ return dictFile.getName();
+ }
+ String dictName = name + "." + locale.toString();
+ if (account != null) {
+ dictName += "." + account;
+ }
+ return dictName;
+ }
+
+ // Note: This method is called by {@link DictionaryFacilitator} using Java reflection.
+ @SuppressWarnings("unused")
+ @ExternallyReferenced
+ public static UserHistoryDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile, final String dictNamePrefix, @Nullable final String account) {
+ return PersonalizationHelper.getUserHistoryDictionary(context, locale, account);
+ }
+
+ /**
+ * Add a word to the user history dictionary.
+ *
+ * @param userHistoryDictionary the user history dictionary
+ * @param ngramContext the n-gram context
+ * @param word the word the user inputted
+ * @param isValid whether the word is valid or not
+ * @param timestamp the timestamp when the word has been inputted
+ */
+ public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
+ @Nonnull final NgramContext ngramContext, final String word, final boolean isValid,
+ final int timestamp) {
+ if (word.length() > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH) {
+ return;
+ }
+ userHistoryDictionary.updateEntriesForWord(ngramContext, word,
+ isValid, 1 /* count */, timestamp);
+ }
+
+ @Override
+ public void close() {
+ // Flush pending writes.
+ asyncFlushBinaryDictionary();
+ super.close();
+ }
+
+ @Override
+ protected Map<String, String> getHeaderAttributeMap() {
+ final Map<String, String> attributeMap = super.getHeaderAttributeMap();
+ attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY,
+ DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
+ DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
+ return attributeMap;
+ }
+
+ @Override
+ protected void loadInitialContentsLocked() {
+ // No initial contents.
+ }
+
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
+ }
+}