Merge "TextClassifier: Switch model based on locale" into oc-dev
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index 35c9a29..5487965 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -44,8 +44,6 @@
private final Object mLangIdLock = new Object();
private final Context mContext;
- // TODO: Implement a way to close the file descriptors.
- private ParcelFileDescriptor mSmartSelectionFd;
private ParcelFileDescriptor mLangIdFd;
private TextClassifier mDefault;
private LangId mLangId;
@@ -61,15 +59,7 @@
public TextClassifier getDefaultTextClassifier() {
synchronized (mTextClassifierLock) {
if (mDefault == null) {
- try {
- mSmartSelectionFd = ParcelFileDescriptor.open(
- new File("/etc/textclassifier/textclassifier.smartselection.en.model"),
- ParcelFileDescriptor.MODE_READ_ONLY);
- mDefault = new TextClassifierImpl(mContext, mSmartSelectionFd);
- } catch (FileNotFoundException e) {
- Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e);
- mDefault = TextClassifier.NO_OP;
- }
+ mDefault = new TextClassifierImpl(mContext);
}
return mDefault;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 66a62c3..f634a1b 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -38,17 +38,24 @@
import android.util.Patterns;
import android.view.View;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Default implementation of the {@link TextClassifier} interface.
@@ -62,16 +69,21 @@
final class TextClassifierImpl implements TextClassifier {
private static final String LOG_TAG = "TextClassifierImpl";
-
- private final Object mSmartSelectionLock = new Object();
+ private static final String MODEL_DIR = "/etc/textclassifier/";
+ private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model";
private final Context mContext;
- private final ParcelFileDescriptor mFd;
+
+ private final Object mSmartSelectionLock = new Object();
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private Map<Locale, String> mModelFilePaths;
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
+ private Locale mLocale;
+ @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
private SmartSelection mSmartSelection;
- TextClassifierImpl(Context context, ParcelFileDescriptor fd) {
+ TextClassifierImpl(Context context) {
mContext = Preconditions.checkNotNull(context);
- mFd = Preconditions.checkNotNull(fd);
}
@Override
@@ -81,15 +93,16 @@
validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
+ final SmartSelection smartSelection = getSmartSelection(defaultLocales);
final String string = text.toString();
- final int[] startEnd = getSmartSelection()
- .suggest(string, selectionStartIndex, selectionEndIndex);
+ final int[] startEnd = smartSelection.suggest(
+ string, selectionStartIndex, selectionEndIndex);
final int start = startEnd[0];
final int end = startEnd[1];
if (start >= 0 && end <= string.length() && start <= end) {
final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
final SmartSelection.ClassificationResult[] results =
- getSmartSelection().classifyText(
+ smartSelection.classifyText(
string, start, end,
getHintFlags(string, start, end));
final int size = results.length;
@@ -120,7 +133,7 @@
try {
if (text.length() > 0) {
final String string = text.toString();
- SmartSelection.ClassificationResult[] results = getSmartSelection()
+ SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
.classifyText(string, startIndex, endIndex,
getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
@@ -147,7 +160,7 @@
Preconditions.checkArgument(text != null);
try {
return LinksInfoFactory.create(
- mContext, getSmartSelection(), text.toString(), linkMask);
+ mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error getting links info.", t);
@@ -156,15 +169,69 @@
return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
}
- private SmartSelection getSmartSelection() throws FileNotFoundException {
+ private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
- if (mSmartSelection == null) {
- mSmartSelection = new SmartSelection(mFd.getFd());
+ localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
+ final Locale locale = findBestSupportedLocaleLocked(localeList);
+ if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
+ destroySmartSelectionIfExistsLocked();
+ mSmartSelection = new SmartSelection(
+ ParcelFileDescriptor.open(
+ // findBestSupportedLocaleLocked should have initialized
+ // mModelFilePaths
+ new File(mModelFilePaths.get(locale)),
+ ParcelFileDescriptor.MODE_READ_ONLY)
+ .getFd());
+ mLocale = locale;
}
return mSmartSelection;
}
}
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ private void destroySmartSelectionIfExistsLocked() {
+ if (mSmartSelection != null) {
+ mSmartSelection.close();
+ mSmartSelection = null;
+ }
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ @Nullable
+ private Locale findBestSupportedLocaleLocked(LocaleList localeList) {
+ final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(
+ new StringJoiner(",")
+ // Specified localeList takes priority over the system default
+ .add(localeList.toLanguageTags())
+ .add(LocaleList.getDefault().toLanguageTags())
+ .toString());
+ return Locale.lookup(languageRangeList, loadModelFilePathsLocked().keySet());
+ }
+
+ @GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
+ private Map<Locale, String> loadModelFilePathsLocked() {
+ if (mModelFilePaths == null) {
+ final Map<Locale, String> modelFilePaths = new HashMap<>();
+ final File modelsDir = new File(MODEL_DIR);
+ if (modelsDir.exists() && modelsDir.isDirectory()) {
+ final File[] models = modelsDir.listFiles();
+ final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
+ final int size = models.length;
+ for (int i = 0; i < size; i++) {
+ final File modelFile = models[i];
+ final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
+ if (matcher.matches() && modelFile.isFile()) {
+ final String language = matcher.group(1);
+ final Locale locale = Locale.forLanguageTag(language);
+ modelFilePaths.put(locale, modelFile.getAbsolutePath());
+ }
+ }
+ }
+ mModelFilePaths = modelFilePaths;
+ }
+ return mModelFilePaths;
+ }
+
private TextClassificationResult createClassificationResult(
SmartSelection.ClassificationResult[] classifications, CharSequence text) {
final TextClassificationResult.Builder builder = new TextClassificationResult.Builder()