summaryrefslogtreecommitdiff
path: root/android/view/textclassifier/TextClassifierImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/view/textclassifier/TextClassifierImpl.java')
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java275
1 files changed, 66 insertions, 209 deletions
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 1c07be4b..2ad6e02c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@ import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Patterns;
-import android.view.View;
import android.widget.TextViewMetrics;
import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.text.BreakIterator;
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;
@@ -100,16 +90,24 @@ final class TextClassifierImpl implements TextClassifier {
@Override
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextSelection.Options options) {
validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
- final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection smartSelection = getSmartSelection(locales);
final String string = text.toString();
- final int[] startEnd = smartSelection.suggest(
- string, selectionStartIndex, selectionEndIndex);
- final int start = startEnd[0];
- final int end = startEnd[1];
+ final int start;
+ final int end;
+ if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) {
+ start = selectionStartIndex;
+ end = selectionEndIndex;
+ } else {
+ final int[] startEnd = smartSelection.suggest(
+ string, selectionStartIndex, selectionEndIndex);
+ start = startEnd[0];
+ end = startEnd[1];
+ }
if (start <= end
&& start >= 0 && end <= string.length()
&& start <= selectionStartIndex && end >= selectionEndIndex) {
@@ -139,18 +137,27 @@ final class TextClassifierImpl implements TextClassifier {
}
// Getting here means something went wrong, return a NO_OP result.
return TextClassifier.NO_OP.suggestSelection(
- text, selectionStartIndex, selectionEndIndex, defaultLocales);
+ text, selectionStartIndex, selectionEndIndex, options);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+ @Nullable LocaleList defaultLocales) {
+ return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+ new TextSelection.Options().setDefaultLocales(defaultLocales));
}
@Override
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextClassification.Options options) {
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
final String string = text.toString();
- SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
.classifyText(string, startIndex, endIndex,
getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
@@ -165,23 +172,41 @@ final class TextClassifierImpl implements TextClassifier {
Log.e(LOG_TAG, "Error getting text classification info.", t);
}
// Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.classifyText(
- text, startIndex, endIndex, defaultLocales);
+ return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options);
}
@Override
- public LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- Preconditions.checkArgument(text != null);
+ public TextClassification classifyText(
+ @NonNull CharSequence text, int startIndex, int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ return classifyText(text, startIndex, endIndex,
+ new TextClassification.Options().setDefaultLocales(defaultLocales));
+ }
+
+ @Override
+ public TextLinks generateLinks(
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ Preconditions.checkNotNull(text);
+ final String textString = text.toString();
+ final TextLinks.Builder builder = new TextLinks.Builder(textString);
try {
- return LinksInfoFactory.create(
- mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
+ LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
+ for (SmartSelection.AnnotatedSpan span : annotations) {
+ final Map<String, Float> entityScores = new HashMap<>();
+ final SmartSelection.ClassificationResult[] results = span.getClassification();
+ for (int i = 0; i < results.length; i++) {
+ entityScores.put(results[i].mCollection, results[i].mScore);
+ }
+ builder.addLink(new TextLinks.TextLink(
+ textString, span.getStartIndex(), span.getEndIndex(), entityScores));
+ }
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error getting links info.", t);
}
- // Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
+ return builder.build();
}
@Override
@@ -210,7 +235,9 @@ final class TextClassifierImpl implements TextClassifier {
if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
destroySmartSelectionIfExistsLocked();
final ParcelFileDescriptor fd = getFdLocked(locale);
- mSmartSelection = new SmartSelection(fd.getFd());
+ final int modelFd = fd.getFd();
+ mVersion = SmartSelection.getVersion(modelFd);
+ mSmartSelection = new SmartSelection(modelFd);
closeAndLogError(fd);
mLocale = locale;
}
@@ -231,18 +258,26 @@ final class TextClassifierImpl implements TextClassifier {
@GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
ParcelFileDescriptor updateFd;
+ int updateVersion = -1;
try {
updateFd = ParcelFileDescriptor.open(
new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (updateFd != null) {
+ updateVersion = SmartSelection.getVersion(updateFd.getFd());
+ }
} catch (FileNotFoundException e) {
updateFd = null;
}
ParcelFileDescriptor factoryFd;
+ int factoryVersion = -1;
try {
final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
if (factoryModelFilePath != null) {
factoryFd = ParcelFileDescriptor.open(
new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (factoryFd != null) {
+ factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+ }
} else {
factoryFd = null;
}
@@ -278,15 +313,11 @@ final class TextClassifierImpl implements TextClassifier {
return factoryFd;
}
- final int updateVersion = SmartSelection.getVersion(updateFdInt);
- final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
if (updateVersion > factoryVersion) {
closeAndLogError(factoryFd);
- mVersion = updateVersion;
return updateFd;
} else {
closeAndLogError(updateFd);
- mVersion = factoryVersion;
return factoryFd;
}
}
@@ -466,180 +497,6 @@ final class TextClassifierImpl implements TextClassifier {
}
/**
- * Detects and creates links for specified text.
- */
- private static final class LinksInfoFactory {
-
- private LinksInfoFactory() {}
-
- public static LinksInfo create(
- Context context, SmartSelection smartSelection, String text, int linkMask) {
- final WordIterator wordIterator = new WordIterator();
- wordIterator.setCharSequence(text, 0, text.length());
- final List<SpanSpec> spans = new ArrayList<>();
- int start = 0;
- int end;
- while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
- final String token = text.substring(start, end);
- if (TextUtils.isEmpty(token)) {
- continue;
- }
-
- final int[] selection = smartSelection.suggest(text, start, end);
- final int selectionStart = selection[0];
- final int selectionEnd = selection[1];
- if (selectionStart >= 0 && selectionEnd <= text.length()
- && selectionStart <= selectionEnd) {
- final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(
- text, selectionStart, selectionEnd,
- getHintFlags(text, selectionStart, selectionEnd));
- if (results.length > 0) {
- final String type = getHighestScoringType(results);
- if (matches(type, linkMask)) {
- // For links without disambiguation, we simply use the default intent.
- final List<Intent> intents = IntentFactory.create(
- context, type, text.substring(selectionStart, selectionEnd));
- if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
- final ClickableSpan span = createSpan(context, intents.get(0));
- spans.add(new SpanSpec(selectionStart, selectionEnd, span));
- }
- }
- }
- }
- start = end;
- }
- return new LinksInfoImpl(text, avoidOverlaps(spans, text));
- }
-
- /**
- * Returns true if the classification type matches the specified linkMask.
- */
- private static boolean matches(String type, int linkMask) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- if ((linkMask & Linkify.PHONE_NUMBERS) != 0
- && TextClassifier.TYPE_PHONE.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
- && TextClassifier.TYPE_EMAIL.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.MAP_ADDRESSES) != 0
- && TextClassifier.TYPE_ADDRESS.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.WEB_URLS) != 0
- && TextClassifier.TYPE_URL.equals(type)) {
- return true;
- }
- return false;
- }
-
- /**
- * Trim the number of spans so that no two spans overlap.
- *
- * This algorithm first ensures that there is only one span per start index, then it
- * makes sure that no two spans overlap.
- */
- private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
- Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
- // Group spans by start index. Take the longest span.
- final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
- final int size = spans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = spans.get(i);
- final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
- if (rep == null || rep.mEnd < span.mEnd) {
- reps.put(span.mStart, span);
- }
- }
- // Avoid span intersections. Take the longer span.
- final LinkedList<SpanSpec> result = new LinkedList<>();
- for (SpanSpec rep : reps.values()) {
- if (result.isEmpty()) {
- result.add(rep);
- continue;
- }
-
- final SpanSpec last = result.getLast();
- if (rep.mStart < last.mEnd) {
- // Spans intersect. Use the one with characters.
- if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
- result.set(result.size() - 1, rep);
- }
- } else {
- result.add(rep);
- }
- }
- return result;
- }
-
- private static ClickableSpan createSpan(final Context context, final Intent intent) {
- return new ClickableSpan() {
- // TODO: Style this span.
- @Override
- public void onClick(View widget) {
- context.startActivity(intent);
- }
- };
- }
-
- private static boolean hasActivityHandler(Context context, Intent intent) {
- if (intent == null) {
- return false;
- }
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
- return resolveInfo != null && resolveInfo.activityInfo != null;
- }
-
- /**
- * Implementation of LinksInfo that adds ClickableSpans to the specified text.
- */
- private static final class LinksInfoImpl implements LinksInfo {
-
- private final CharSequence mOriginalText;
- private final List<SpanSpec> mSpans;
-
- LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
- mOriginalText = originalText;
- mSpans = spans;
- }
-
- @Override
- public boolean apply(@NonNull CharSequence text) {
- Preconditions.checkArgument(text != null);
- if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
- Spannable spannable = (Spannable) text;
- final int size = mSpans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = mSpans.get(i);
- spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
- }
- return true;
- }
- return false;
- }
- }
-
- /**
- * Span plus its start and end index.
- */
- private static final class SpanSpec {
-
- private final int mStart;
- private final int mEnd;
- private final ClickableSpan mSpan;
-
- SpanSpec(int start, int end, ClickableSpan span) {
- mStart = start;
- mEnd = end;
- mSpan = span;
- }
- }
- }
-
- /**
* Creates intents based on the classification type.
*/
private static final class IntentFactory {
@@ -656,8 +513,8 @@ final class TextClassifierImpl implements TextClassifier {
intents.add(new Intent(Intent.ACTION_SENDTO)
.setData(Uri.parse(String.format("mailto:%s", text))));
intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
break;
case TextClassifier.TYPE_PHONE:
intents.add(new Intent(Intent.ACTION_DIAL)