diff options
Diffstat (limited to 'android/view/textclassifier/TextClassifierImpl.java')
-rw-r--r-- | android/view/textclassifier/TextClassifierImpl.java | 275 |
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) |