143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki/*
243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * Copyright (C) 2017 The Android Open Source Project
343e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *
443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * Licensed under the Apache License, Version 2.0 (the "License");
543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * you may not use this file except in compliance with the License.
643e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * You may obtain a copy of the License at
743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *
843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *      http://www.apache.org/licenses/LICENSE-2.0
943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *
1043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * Unless required by applicable law or agreed to in writing, software
1143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * distributed under the License is distributed on an "AS IS" BASIS,
1243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1343e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * See the License for the specific language governing permissions and
1443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * limitations under the License.
1543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki */
1643e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
1743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokipackage android.view.textclassifier;
1843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
19a1652cfcce547183a426cc710691c740b2e46aa7Jan Althausimport static java.time.temporal.ChronoUnit.MILLIS;
20a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus
2143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.annotation.NonNull;
226b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Tokiimport android.annotation.Nullable;
23ad52f4b97c897689d0b4dbfe344229a9970136ebAbodunrinwa Tokiimport android.annotation.WorkerThread;
24253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Tokiimport android.app.PendingIntent;
2520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althausimport android.app.RemoteAction;
26705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althausimport android.app.SearchManager;
27fafdb7372fe0da277a32b47cff825dbb244e4af0Abodunrinwa Tokiimport android.content.ComponentName;
28705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althausimport android.content.ContentUris;
2943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.content.Context;
3043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.content.Intent;
3143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.content.pm.PackageManager;
3243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.content.pm.ResolveInfo;
3320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althausimport android.graphics.drawable.Icon;
3443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.net.Uri;
3505e0051d1d3000525a12f555c59097201487fad8Jan Althausimport android.os.Bundle;
364cfda0beefc7f21f1818c689ca54dd11480c68f2Abodunrinwa Tokiimport android.os.LocaleList;
3743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport android.os.ParcelFileDescriptor;
3805e0051d1d3000525a12f555c59097201487fad8Jan Althausimport android.os.UserManager;
399b4c82a83cc3c1aafac2325d7a601ba3e090b90bAbodunrinwa Tokiimport android.provider.Browser;
40705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althausimport android.provider.CalendarContract;
4192d76838f8122917c51a2ea7f04fa04d7c9275a9Jan Althausimport android.provider.ContactsContract;
4243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
43c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport com.android.internal.annotations.GuardedBy;
4443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport com.android.internal.util.Preconditions;
4543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
46c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport java.io.File;
4743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Tokiimport java.io.FileNotFoundException;
48146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Tokiimport java.io.IOException;
49eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althausimport java.io.UnsupportedEncodingException;
50eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althausimport java.net.URLEncoder;
51a1652cfcce547183a426cc710691c740b2e46aa7Jan Althausimport java.time.Instant;
52a1652cfcce547183a426cc710691c740b2e46aa7Jan Althausimport java.time.ZonedDateTime;
536b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Tokiimport java.util.ArrayList;
54db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledleyimport java.util.Arrays;
55db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledleyimport java.util.Collection;
56db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledleyimport java.util.Collections;
57c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport java.util.HashMap;
586b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Tokiimport java.util.List;
599b4c82a83cc3c1aafac2325d7a601ba3e090b90bAbodunrinwa Tokiimport java.util.Locale;
606b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Tokiimport java.util.Map;
61c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport java.util.Objects;
62ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althausimport java.util.StringJoiner;
63705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althausimport java.util.concurrent.TimeUnit;
64c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport java.util.regex.Matcher;
65c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Tokiimport java.util.regex.Pattern;
6643e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
6743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki/**
6843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * Default implementation of the {@link TextClassifier} interface.
6943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *
7043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * <p>This class uses machine learning to recognize entities in text.
7143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * Unless otherwise stated, methods of this class are blocking operations and should most
7243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * likely not be called on the UI thread.
7343e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki *
7443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki * @hide
7543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki */
76d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Tokipublic final class TextClassifierImpl implements TextClassifier {
7743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
78692b196cc12f6852b0bb9009c882a69b67dda4d8Abodunrinwa Toki    private static final String LOG_TAG = DEFAULT_LOG_TAG;
79c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki    private static final String MODEL_DIR = "/etc/textclassifier/";
80abb4fc8464cb26b24e6ef046672930a02fadbe52Jan Althaus    private static final String MODEL_FILE_REGEX = "textclassifier\\.(.*)\\.model";
81146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki    private static final String UPDATED_MODEL_FILE_PATH =
8267d234d726a7101d48faa379cd4b02b1cde98574Jan Althaus            "/data/misc/textclassifier/textclassifier.model";
83b89cf026cf731108daa293f076ffcb32bbd2c8d7Abodunrinwa Toki
8443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    private final Context mContext;
85d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    private final TextClassifier mFallback;
8631efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus    private final GenerateLinksLogger mGenerateLinksLogger;
871d77557517918258cbf4264e647a138d1b9b648fAbodunrinwa Toki
88d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    private final Object mLock = new Object();
89d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not access outside this lock.
90ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    private List<ModelFile> mAllModelFiles;
91d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not access outside this lock.
92ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    private ModelFile mModel;
93d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not access outside this lock.
94f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka    private TextClassifierImplNative mNative;
9543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
963bb443613820c7e54512cef9659ef2e9428243c6Abodunrinwa Toki    private final Object mLoggerLock = new Object();
973bb443613820c7e54512cef9659ef2e9428243c6Abodunrinwa Toki    @GuardedBy("mLoggerLock") // Do not access outside this lock.
985a03094ebc91df1c64a2232be648ac3ed26657ceJan Althaus    private SelectionSessionLogger mSessionLogger;
993bb443613820c7e54512cef9659ef2e9428243c6Abodunrinwa Toki
100db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki    private final TextClassificationConstants mSettings;
1010e6b43ed58074725d8021401ded8e22a685d15e1Abodunrinwa Toki
102253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki    public TextClassifierImpl(
103253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            Context context, TextClassificationConstants settings, TextClassifier fallback) {
10443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        mContext = Preconditions.checkNotNull(context);
105253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki        mFallback = Preconditions.checkNotNull(fallback);
106db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki        mSettings = Preconditions.checkNotNull(settings);
107db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki        mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate());
10843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    }
10943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
110253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki    public TextClassifierImpl(Context context, TextClassificationConstants settings) {
111253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki        this(context, settings, TextClassifier.NO_OP);
112253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki    }
113253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki
114d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    /** @inheritDoc */
11543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    @Override
116ad52f4b97c897689d0b4dbfe344229a9970136ebAbodunrinwa Toki    @WorkerThread
117080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki    public TextSelection suggestSelection(TextSelection.Request request) {
118080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Preconditions.checkNotNull(request);
119080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Utils.checkMainThread();
12043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        try {
121080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final int rangeLength = request.getEndIndex() - request.getStartIndex();
122080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final String string = request.getText().toString();
123080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            if (string.length() > 0
124db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki                    && rangeLength <= mSettings.getSuggestSelectionMaxRangeLength()) {
125080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                final String localesString = concatenateLocales(request.getDefaultLocales());
126a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                final ZonedDateTime refTime = ZonedDateTime.now();
127080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                final TextClassifierImplNative nativeImpl = getNative(request.getDefaultLocales());
1282b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                final int start;
1292b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                final int end;
130080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                if (mSettings.isModelDarkLaunchEnabled() && !request.isDarkLaunchAllowed()) {
131080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    start = request.getStartIndex();
132080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    end = request.getEndIndex();
1332b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                } else {
134f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                    final int[] startEnd = nativeImpl.suggestSelection(
135080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                            string, request.getStartIndex(), request.getEndIndex(),
136f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                            new TextClassifierImplNative.SelectionOptions(localesString));
1372b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                    start = startEnd[0];
1382b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                    end = startEnd[1];
1392b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki                }
140b7f7d369777b8475ff7f4c6eaa4ef1452f48d712Jan Althaus                if (start < end
141b416297433c91ff6694fdf5ce41f7ab6f1ee346bAbodunrinwa Toki                        && start >= 0 && end <= string.length()
142080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                        && start <= request.getStartIndex() && end >= request.getEndIndex()) {
143692b196cc12f6852b0bb9009c882a69b67dda4d8Abodunrinwa Toki                    final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
144f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                    final TextClassifierImplNative.ClassificationResult[] results =
145f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                            nativeImpl.classifyText(
146d2d1399a62087b4e9e36aca5d53f628b4c06e1d7Abodunrinwa Toki                                    string, start, end,
147f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                    new TextClassifierImplNative.ClassificationOptions(
148a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                            refTime.toInstant().toEpochMilli(),
149a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                            refTime.getZone().getId(),
150f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                            localesString));
151a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki                    final int size = results.length;
152a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki                    for (int i = 0; i < size; i++) {
153f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        tsBuilder.setEntityType(results[i].getCollection(), results[i].getScore());
154a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki                    }
155080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    return tsBuilder.setId(createId(
156080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                            string, request.getStartIndex(), request.getEndIndex()))
157692b196cc12f6852b0bb9009c882a69b67dda4d8Abodunrinwa Toki                            .build();
15843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                } else {
15943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                    // We can not trust the result. Log the issue and ignore the result.
16043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                    Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result.");
16143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                }
16243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki            }
16343e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        } catch (Throwable t) {
16443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki            // Avoid throwing from this method. Log the error.
16543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki            Log.e(LOG_TAG,
16643e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                    "Error suggesting selection for text. No changes to selection suggested.",
16743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                    t);
16843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        }
16943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        // Getting here means something went wrong, return a NO_OP result.
170080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        return mFallback.suggestSelection(request);
1712b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki    }
1722b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki
173d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    /** @inheritDoc */
1742b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki    @Override
175ad52f4b97c897689d0b4dbfe344229a9970136ebAbodunrinwa Toki    @WorkerThread
176080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki    public TextClassification classifyText(TextClassification.Request request) {
177080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Preconditions.checkNotNull(request);
178080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Utils.checkMainThread();
17943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        try {
180080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final int rangeLength = request.getEndIndex() - request.getStartIndex();
181080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final String string = request.getText().toString();
182080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            if (string.length() > 0 && rangeLength <= mSettings.getClassifyTextMaxRangeLength()) {
183080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                final String localesString = concatenateLocales(request.getDefaultLocales());
184080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                final ZonedDateTime refTime = request.getReferenceTime() != null
185080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                        ? request.getReferenceTime() : ZonedDateTime.now();
186f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                final TextClassifierImplNative.ClassificationResult[] results =
187080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                        getNative(request.getDefaultLocales())
188080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                                .classifyText(
189080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                                        string, request.getStartIndex(), request.getEndIndex(),
190f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                        new TextClassifierImplNative.ClassificationOptions(
191a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                                refTime.toInstant().toEpochMilli(),
192a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                                refTime.getZone().getId(),
193f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                                localesString));
194a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki                if (results.length > 0) {
195705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                    return createClassificationResult(
196080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                            results, string,
197080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                            request.getStartIndex(), request.getEndIndex(), refTime.toInstant());
19843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki                }
19943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki            }
20043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        } catch (Throwable t) {
20143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki            // Avoid throwing from this method. Log the error.
202786188536ff6f30feedf84764248047c9da76760Abodunrinwa Toki            Log.e(LOG_TAG, "Error getting text classification info.", t);
20343e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        }
20443e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        // Getting here means something went wrong, return a NO_OP result.
205080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        return mFallback.classifyText(request);
2062b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki    }
2072b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki
208d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    /** @inheritDoc */
2092b6020fc80dc239114ecc7ecd501d382d4883641Abodunrinwa Toki    @Override
210ad52f4b97c897689d0b4dbfe344229a9970136ebAbodunrinwa Toki    @WorkerThread
211080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki    public TextLinks generateLinks(@NonNull TextLinks.Request request) {
212080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Preconditions.checkNotNull(request);
213080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength());
214080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        Utils.checkMainThread();
2159cfa60673a60bea9c2ee7daa72d72a9e12484686Richard Ledley
216080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
217080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            return Utils.generateLegacyLinks(request);
2189cfa60673a60bea9c2ee7daa72d72a9e12484686Richard Ledley        }
2199cfa60673a60bea9c2ee7daa72d72a9e12484686Richard Ledley
220080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        final String textString = request.getText().toString();
2216563833cf3c79e8cd211e32357422ae899674437Abodunrinwa Toki        final TextLinks.Builder builder = new TextLinks.Builder(textString);
2226563833cf3c79e8cd211e32357422ae899674437Abodunrinwa Toki
22368d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley        try {
22431efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus            final long startTimeMs = System.currentTimeMillis();
225a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            final ZonedDateTime refTime = ZonedDateTime.now();
226080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final Collection<String> entitiesToIdentify = request.getEntityConfig() != null
227080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    ? request.getEntityConfig().resolveEntityListModifications(
228080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                            getEntitiesForHints(request.getEntityConfig().getHints()))
229080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    : mSettings.getEntityListDefault();
230f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            final TextClassifierImplNative nativeImpl =
231080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    getNative(request.getDefaultLocales());
232f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            final TextClassifierImplNative.AnnotatedSpan[] annotations =
233f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                    nativeImpl.annotate(
234f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        textString,
235f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        new TextClassifierImplNative.AnnotationOptions(
236a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                refTime.toInstant().toEpochMilli(),
237080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                                        refTime.getZone().getId(),
238080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                                concatenateLocales(request.getDefaultLocales())));
239f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            for (TextClassifierImplNative.AnnotatedSpan span : annotations) {
240f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                final TextClassifierImplNative.ClassificationResult[] results =
241f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        span.getClassification();
242f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                if (results.length == 0
243f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        || !entitiesToIdentify.contains(results[0].getCollection())) {
244db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledley                    continue;
245db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledley                }
246db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledley                final Map<String, Float> entityScores = new HashMap<>();
24768d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley                for (int i = 0; i < results.length; i++) {
248f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                    entityScores.put(results[i].getCollection(), results[i].getScore());
24968d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley                }
250fe20cdd9101c68031a7174c597a43030e167e3b4Abodunrinwa Toki                builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores);
25168d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley            }
25231efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus            final TextLinks links = builder.build();
25331efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus            final long endTimeMs = System.currentTimeMillis();
254080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki            final String callingPackageName = request.getCallingPackageName() == null
255080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    ? mContext.getPackageName()  // local (in process) TC.
256080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    : request.getCallingPackageName();
25731efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus            mGenerateLinksLogger.logGenerateLinks(
258080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki                    request.getText(), links, callingPackageName, endTimeMs - startTimeMs);
25931efdc385c8fef30e0cda2863b3d5c3c995d55c5Jan Althaus            return links;
26068d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley        } catch (Throwable t) {
26168d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley            // Avoid throwing from this method. Log the error.
26268d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley            Log.e(LOG_TAG, "Error getting links info.", t);
26368d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley        }
264080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        return mFallback.generateLinks(request);
26568d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley    }
26668d945234667d11a65a924a7cdc1f5753a41a80fRichard Ledley
267108aad3c3036e61850cd39f55687626674f47d30Jan Althaus    /** @inheritDoc */
268108aad3c3036e61850cd39f55687626674f47d30Jan Althaus    @Override
269108aad3c3036e61850cd39f55687626674f47d30Jan Althaus    public int getMaxGenerateLinksTextLength() {
270db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki        return mSettings.getGenerateLinksMaxTextLength();
271108aad3c3036e61850cd39f55687626674f47d30Jan Althaus    }
272108aad3c3036e61850cd39f55687626674f47d30Jan Althaus
2731fc998b0fda051188665e599c891da4a5750581dRichard Ledley    private Collection<String> getEntitiesForHints(Collection<String> hints) {
2740aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        final boolean editable = hints.contains(HINT_TEXT_IS_EDITABLE);
2750aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        final boolean notEditable = hints.contains(HINT_TEXT_IS_NOT_EDITABLE);
2760aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus
2770aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        // Use the default if there is no hint, or conflicting ones.
2780aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        final boolean useDefault = editable == notEditable;
2790aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        if (useDefault) {
280db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki            return mSettings.getEntityListDefault();
2810aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        } else if (editable) {
282db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki            return mSettings.getEntityListEditable();
2830aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        } else {  // notEditable
284db8fc314d2ac9a2ce3209fe9e842c985e6f57d06Abodunrinwa Toki            return mSettings.getEntityListNotEditable();
2850aacdb665cc42a64b5ecdfe1011bb3f19fea6d58Jan Althaus        }
286db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledley    }
287db18a578f1aa2e39f88a53eab962ed6470ca2fe1Richard Ledley
28888be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki    @Override
28988be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki    public void onSelectionEvent(SelectionEvent event) {
29088be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki        Preconditions.checkNotNull(event);
29188be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki        synchronized (mLoggerLock) {
2925a03094ebc91df1c64a2232be648ac3ed26657ceJan Althaus            if (mSessionLogger == null) {
2935a03094ebc91df1c64a2232be648ac3ed26657ceJan Althaus                mSessionLogger = new SelectionSessionLogger();
29488be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki            }
2955a03094ebc91df1c64a2232be648ac3ed26657ceJan Althaus            mSessionLogger.writeEvent(event);
29688be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki        }
29788be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki    }
29888be5a6cee59868eaee6f7b52fd8b2e6f6f28429Abodunrinwa Toki
299f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka    private TextClassifierImplNative getNative(LocaleList localeList)
300f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            throws FileNotFoundException {
301d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki        synchronized (mLock) {
302c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki            localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
303ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            final ModelFile bestModel = findBestModelLocked(localeList);
304ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            if (bestModel == null) {
305ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                throw new FileNotFoundException("No model for " + localeList.toLanguageTags());
306146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki            }
307f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            if (mNative == null || !Objects.equals(mModel, bestModel)) {
308ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                Log.d(DEFAULT_LOG_TAG, "Loading " + bestModel);
309f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                destroyNativeIfExistsLocked();
310ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final ParcelFileDescriptor fd = ParcelFileDescriptor.open(
311ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                        new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY);
312f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                mNative = new TextClassifierImplNative(fd.getFd());
3136ace8930263b55fe7e9ff92468a14524c4e68407Abodunrinwa Toki                closeAndLogError(fd);
314ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                mModel = bestModel;
315b89cf026cf731108daa293f076ffcb32bbd2c8d7Abodunrinwa Toki            }
316f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            return mNative;
31743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        }
31843e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    }
31943e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
320080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki    private String createId(String text, int start, int end) {
321d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki        synchronized (mLock) {
3225a03094ebc91df1c64a2232be648ac3ed26657ceJan Althaus            return SelectionSessionLogger.createId(text, start, end, mContext, mModel.getVersion(),
323ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    mModel.getSupportedLocales());
324146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        }
325146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki    }
326146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki
327d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not call outside this lock.
328f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka    private void destroyNativeIfExistsLocked() {
329f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka        if (mNative != null) {
330f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            mNative.close();
331f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            mNative = null;
332c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki        }
333c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki    }
334c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki
335f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka    private static String concatenateLocales(@Nullable LocaleList locales) {
336f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka        return (locales == null) ? "" : locales.toLanguageTags();
337f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka    }
338f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka
339ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    /**
340ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * Finds the most appropriate model to use for the given target locale list.
341ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     *
342ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * The basic logic is: we ignore all models that don't support any of the target locales. For
343ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * the remaining candidates, we take the update model unless its version number is lower than
344ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * the factory version. It's assumed that factory models do not have overlapping locale ranges
345ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * and conflict resolution between these models hence doesn't matter.
346ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     */
347d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not call outside this lock.
348c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki    @Nullable
349ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    private ModelFile findBestModelLocked(LocaleList localeList) {
350a2df6e5415d2481dfda96c07acb24c7898abd80aAbodunrinwa Toki        // Specified localeList takes priority over the system default, so it is listed first.
351a2df6e5415d2481dfda96c07acb24c7898abd80aAbodunrinwa Toki        final String languages = localeList.isEmpty()
352a2df6e5415d2481dfda96c07acb24c7898abd80aAbodunrinwa Toki                ? LocaleList.getDefault().toLanguageTags()
353a2df6e5415d2481dfda96c07acb24c7898abd80aAbodunrinwa Toki                : localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags();
354a2df6e5415d2481dfda96c07acb24c7898abd80aAbodunrinwa Toki        final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages);
355146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki
356ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        ModelFile bestModel = null;
357ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        for (ModelFile model : listAllModelsLocked()) {
358ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            if (model.isAnyLanguageSupported(languageRangeList)) {
3590fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                if (model.isPreferredTo(bestModel)) {
360ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    bestModel = model;
361ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                }
362ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            }
363146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        }
364ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        return bestModel;
365c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki    }
366c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki
367ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    /** Returns a list of all model files available, in order of precedence. */
368d32906c202db3b84151c310ecd89a07bb41208f7Abodunrinwa Toki    @GuardedBy("mLock") // Do not call outside this lock.
369ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    private List<ModelFile> listAllModelsLocked() {
370ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        if (mAllModelFiles == null) {
371ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            final List<ModelFile> allModels = new ArrayList<>();
372ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            // The update model has the highest precedence.
373ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            if (new File(UPDATED_MODEL_FILE_PATH).exists()) {
374ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final ModelFile updatedModel = ModelFile.fromPath(UPDATED_MODEL_FILE_PATH);
375ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                if (updatedModel != null) {
376ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    allModels.add(updatedModel);
377ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                }
378ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            }
379ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            // Factory models should never have overlapping locales, so the order doesn't matter.
380c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki            final File modelsDir = new File(MODEL_DIR);
381c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki            if (modelsDir.exists() && modelsDir.isDirectory()) {
382ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final File[] modelFiles = modelsDir.listFiles();
383c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki                final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX);
384ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                for (File modelFile : modelFiles) {
385c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki                    final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName());
386c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki                    if (matcher.matches() && modelFile.isFile()) {
387ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                        final ModelFile model = ModelFile.fromPath(modelFile.getAbsolutePath());
388ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                        if (model != null) {
389ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                            allModels.add(model);
390ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                        }
391c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki                    }
392c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki                }
393c39006a1cdef46a3bd36b7a0e9bb9489564ef944Abodunrinwa Toki            }
394ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            mAllModelFiles = allModels;
395146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        }
396ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        return mAllModelFiles;
397146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki    }
398146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki
399e0b57893c68a685d287664df28467694a39e4432Abodunrinwa Toki    private TextClassification createClassificationResult(
400f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            TextClassifierImplNative.ClassificationResult[] classifications,
401a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            String text, int start, int end, @Nullable Instant referenceTime) {
402008f387e8344f9f98149856cd737086c14752f4dAbodunrinwa Toki        final String classifiedText = text.substring(start, end);
403e0b57893c68a685d287664df28467694a39e4432Abodunrinwa Toki        final TextClassification.Builder builder = new TextClassification.Builder()
404008f387e8344f9f98149856cd737086c14752f4dAbodunrinwa Toki                .setText(classifiedText);
405a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki
406a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki        final int size = classifications.length;
407f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka        TextClassifierImplNative.ClassificationResult highestScoringResult = null;
408705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        float highestScore = Float.MIN_VALUE;
409a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki        for (int i = 0; i < size; i++) {
410f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            builder.setEntityType(classifications[i].getCollection(),
411f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                  classifications[i].getScore());
412f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            if (classifications[i].getScore() > highestScore) {
413705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                highestScoringResult = classifications[i];
414f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                highestScore = classifications[i].getScore();
415705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            }
416a6096f6c4c56c4937b52efb593d4333db301d6adAbodunrinwa Toki        }
4179b4c82a83cc3c1aafac2325d7a601ba3e090b90bAbodunrinwa Toki
41820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        boolean isPrimaryAction = true;
41920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        for (LabeledIntent labeledIntent : IntentFactory.create(
42020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                mContext, referenceTime, highestScoringResult, classifiedText)) {
421904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki            final RemoteAction action = labeledIntent.asRemoteAction(mContext);
422253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            if (action == null) {
423253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki                continue;
424253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            }
42520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            if (isPrimaryAction) {
42620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                // For O backwards compatibility, the first RemoteAction is also written to the
42720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                // legacy API fields.
42820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                builder.setIcon(action.getIcon().loadDrawable(mContext));
42920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                builder.setLabel(action.getTitle().toString());
43020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                builder.setIntent(labeledIntent.getIntent());
43120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                builder.setOnClickListener(TextClassification.createIntentOnClickListener(
43220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        TextClassification.createPendingIntent(mContext,
433904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                labeledIntent.getIntent(), labeledIntent.getRequestCode())));
43420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                isPrimaryAction = false;
435fafdb7372fe0da277a32b47cff825dbb244e4af0Abodunrinwa Toki            }
43620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            builder.addAction(action);
43743e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki        }
43820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
439080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        return builder.setId(createId(text, start, end)).build();
44043e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    }
44143e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki
44243e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki    /**
443146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki     * Closes the ParcelFileDescriptor and logs any errors that occur.
444146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki     */
445146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki    private static void closeAndLogError(ParcelFileDescriptor fd) {
446146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        try {
447146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki            fd.close();
448146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        } catch (IOException e) {
449146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki            Log.e(LOG_TAG, "Error closing file.", e);
450146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki        }
451146d0d4f6dbab461d1c8e53a65ccddca5041b767Abodunrinwa Toki    }
4526b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki
4536b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki    /**
454ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     * Describes TextClassifier model files on disk.
455ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus     */
456ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    private static final class ModelFile {
457ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
458ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        private final String mPath;
459ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        private final String mName;
460ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        private final int mVersion;
461ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        private final List<Locale> mSupportedLocales;
4620fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka        private final boolean mLanguageIndependent;
463ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
464ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        /** Returns null if the path did not point to a compatible model. */
465ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        static @Nullable ModelFile fromPath(String path) {
466ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            final File file = new File(path);
467ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            try {
468ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final ParcelFileDescriptor modelFd = ParcelFileDescriptor.open(
469ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                        file, ParcelFileDescriptor.MODE_READ_ONLY);
470f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                final int version = TextClassifierImplNative.getVersion(modelFd.getFd());
471f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                final String supportedLocalesStr =
472f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                        TextClassifierImplNative.getLocales(modelFd.getFd());
473ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                if (supportedLocalesStr.isEmpty()) {
474ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    Log.d(DEFAULT_LOG_TAG, "Ignoring " + file.getAbsolutePath());
475ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    return null;
476ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                }
4770fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                final boolean languageIndependent = supportedLocalesStr.equals("*");
478ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final List<Locale> supportedLocales = new ArrayList<>();
479ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                for (String langTag : supportedLocalesStr.split(",")) {
480ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    supportedLocales.add(Locale.forLanguageTag(langTag));
481ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                }
482ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                closeAndLogError(modelFd);
4830fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                return new ModelFile(path, file.getName(), version, supportedLocales,
4840fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                                     languageIndependent);
485ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            } catch (FileNotFoundException e) {
486ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                Log.e(DEFAULT_LOG_TAG, "Failed to peek " + file.getAbsolutePath(), e);
487ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                return null;
488ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            }
489ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
490ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
491ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        /** The absolute path to the model file. */
492ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        String getPath() {
493ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            return mPath;
494ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
495ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
496080c8542b68cf17a0441862c404cb49ce0e86cfeAbodunrinwa Toki        /** A name to use for id generation. Effectively the name of the model file. */
497ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        String getName() {
498ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            return mName;
499ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
500ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
501ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        /** Returns the version tag in the model's metadata. */
502ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        int getVersion() {
503ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            return mVersion;
504ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
505ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
506ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        /** Returns whether the language supports any language in the given ranges. */
507ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
5080fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            return mLanguageIndependent || Locale.lookup(languageRanges, mSupportedLocales) != null;
509ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
510ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
511ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        /** All locales supported by the model. */
512ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        List<Locale> getSupportedLocales() {
513ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            return Collections.unmodifiableList(mSupportedLocales);
514ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
515ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
5160fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka        public boolean isPreferredTo(ModelFile model) {
5170fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            // A model is preferred to no model.
5180fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            if (model == null) {
5190fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                return true;
5200fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            }
5210fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka
5220fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            // A language-specific model is preferred to a language independent
5230fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            // model.
5240fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            if (!mLanguageIndependent && model.mLanguageIndependent) {
5250fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                return true;
5260fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            }
5270fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka
5280fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            // A higher-version model is preferred.
5290fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            if (getVersion() > model.getVersion()) {
5300fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                return true;
5310fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            }
5320fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            return false;
5330fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka        }
5340fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka
535ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        @Override
536ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        public boolean equals(Object other) {
537ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            if (this == other) {
538ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                return true;
539ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            } else if (other == null || !ModelFile.class.isAssignableFrom(other.getClass())) {
540ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                return false;
541ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            } else {
542ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                final ModelFile otherModel = (ModelFile) other;
543ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                return mPath.equals(otherModel.mPath);
544ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            }
545ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
546ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
547ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        @Override
548ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        public String toString() {
549ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            final StringJoiner localesJoiner = new StringJoiner(",");
550ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            for (Locale locale : mSupportedLocales) {
551ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                localesJoiner.add(locale.toLanguageTag());
552ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            }
553ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            return String.format(Locale.US, "ModelFile { path=%s name=%s version=%d locales=%s }",
554ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus                    mPath, mName, mVersion, localesJoiner.toString());
555ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
556ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
5570fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka        private ModelFile(String path, String name, int version, List<Locale> supportedLocales,
5580fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka                          boolean languageIndependent) {
559ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            mPath = path;
560ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            mName = name;
561ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            mVersion = version;
562ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus            mSupportedLocales = supportedLocales;
5630fcacdddf466b22cc4fdbb3a16992bc34686cacbLukas Zilka            mLanguageIndependent = languageIndependent;
564ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus        }
565ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    }
566ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus
567ef0156d707aa7f7514ed008ce3116c5d4ff73006Jan Althaus    /**
56820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus     * Helper class to store the information from which RemoteActions are built.
56920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus     */
57020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus    private static final class LabeledIntent {
57120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
572904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        static final int DEFAULT_REQUEST_CODE = 0;
573904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki
574904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        private final String mTitle;
575904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        private final String mDescription;
576904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        private final Intent mIntent;
577904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        private final int mRequestCode;
578904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki
579904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        /**
580904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * Initializes a LabeledIntent.
581904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         *
582904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * <p>NOTE: {@code reqestCode} is required to not be {@link #DEFAULT_REQUEST_CODE}
583904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * if distinguishing info (e.g. the classified text) is represented in intent extras only.
584904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * In such circumstances, the request code should represent the distinguishing info
585904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * (e.g. by generating a hashcode) so that the generated PendingIntent is (somewhat)
586904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * unique. To be correct, the PendingIntent should be definitely unique but we try a
587904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         * best effort approach that avoids spamming the system with PendingIntents.
588904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki         */
589904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        // TODO: Fix the issue mentioned above so the behaviour is correct.
590904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        LabeledIntent(String title, String description, Intent intent, int requestCode) {
59120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            mTitle = title;
59220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            mDescription = description;
59320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            mIntent = intent;
594904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki            mRequestCode = requestCode;
59520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        }
59620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
59720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        String getTitle() {
59820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return mTitle;
59920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        }
60020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
60120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        String getDescription() {
60220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return mDescription;
60320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        }
60420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
60520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        Intent getIntent() {
60620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return mIntent;
60720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        }
60820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
609904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        int getRequestCode() {
610904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki            return mRequestCode;
611904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki        }
612904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki
613253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki        @Nullable
61420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        RemoteAction asRemoteAction(Context context) {
61520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            final PackageManager pm = context.getPackageManager();
61620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            final ResolveInfo resolveInfo = pm.resolveActivity(mIntent, 0);
61720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            final String packageName = resolveInfo != null && resolveInfo.activityInfo != null
61820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    ? resolveInfo.activityInfo.packageName : null;
61920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            Icon icon = null;
62020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            boolean shouldShowIcon = false;
62120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            if (packageName != null && !"android".equals(packageName)) {
62220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                // There is a default activity handling the intent.
62320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                mIntent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
62420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                if (resolveInfo.activityInfo.getIconResource() != 0) {
62520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    icon = Icon.createWithResource(
62620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            packageName, resolveInfo.activityInfo.getIconResource());
62720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    shouldShowIcon = true;
62820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                }
62920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            }
63020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            if (icon == null) {
63120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                // RemoteAction requires that there be an icon.
63220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                icon = Icon.createWithResource("android",
63320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        com.android.internal.R.drawable.ic_more_items);
63420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            }
635253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            final PendingIntent pendingIntent =
636253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki                    TextClassification.createPendingIntent(context, mIntent, mRequestCode);
637253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            if (pendingIntent == null) {
638253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki                return null;
639253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            }
640253827f207be31399a21c390f90ce3ffe4b020c0Abodunrinwa Toki            final RemoteAction action = new RemoteAction(icon, mTitle, mDescription, pendingIntent);
64120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            action.setShouldShowIcon(shouldShowIcon);
64220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return action;
64320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        }
64420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus    }
64520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus
64620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus    /**
6476b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki     * Creates intents based on the classification type.
6486b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki     */
649705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus    static final class IntentFactory {
650705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
651705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5);
652705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1);
6536b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki
6546b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki        private IntentFactory() {}
6556b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki
65692d76838f8122917c51a2ea7f04fa04d7c9275a9Jan Althaus        @NonNull
65720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        public static List<LabeledIntent> create(
658705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                Context context,
659a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                @Nullable Instant referenceTime,
660f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                TextClassifierImplNative.ClassificationResult classification,
661705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                String text) {
662f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka            final String type = classification.getCollection().trim().toLowerCase(Locale.ENGLISH);
66370d41cd792cbbc1eb6b2c36be54cfcae7b53c03aAbodunrinwa Toki            text = text.trim();
6646b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki            switch (type) {
6656b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki                case TextClassifier.TYPE_EMAIL:
66620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    return createForEmail(context, text);
6676b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki                case TextClassifier.TYPE_PHONE:
66805e0051d1d3000525a12f555c59097201487fad8Jan Althaus                    return createForPhone(context, text);
6696b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki                case TextClassifier.TYPE_ADDRESS:
67020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    return createForAddress(context, text);
6719b4c82a83cc3c1aafac2325d7a601ba3e090b90bAbodunrinwa Toki                case TextClassifier.TYPE_URL:
672705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                    return createForUrl(context, text);
673705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                case TextClassifier.TYPE_DATE:
674705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                case TextClassifier.TYPE_DATE_TIME:
675f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                    if (classification.getDatetimeResult() != null) {
676a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                        final Instant parsedTime = Instant.ofEpochMilli(
677f8c36bffb8921803d19d7c16d7eaf1dec883ff9dLukas Zilka                                classification.getDatetimeResult().getTimeMsUtc());
678a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                        return createForDatetime(context, type, referenceTime, parsedTime);
67986ef9827dabc05832997898e8d85504e007a206bAbodunrinwa Toki                    } else {
680705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                        return new ArrayList<>();
68170d41cd792cbbc1eb6b2c36be54cfcae7b53c03aAbodunrinwa Toki                    }
682705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                case TextClassifier.TYPE_FLIGHT_NUMBER:
68320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    return createForFlight(context, text);
684705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                default:
685705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                    return new ArrayList<>();
686705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            }
687705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
688705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
689705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
69020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForEmail(Context context, String text) {
691705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            return Arrays.asList(
69220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new LabeledIntent(
69320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            context.getString(com.android.internal.R.string.email),
69420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            context.getString(com.android.internal.R.string.email_desc),
69520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            new Intent(Intent.ACTION_SENDTO)
696904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                    .setData(Uri.parse(String.format("mailto:%s", text))),
697904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                            LabeledIntent.DEFAULT_REQUEST_CODE),
69820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new LabeledIntent(
69920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            context.getString(com.android.internal.R.string.add_contact),
70020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            context.getString(com.android.internal.R.string.add_contact_desc),
70120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            new Intent(Intent.ACTION_INSERT_OR_EDIT)
70220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
703904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                    .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
704904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                            text.hashCode()));
705705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
706705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
707705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
70820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForPhone(Context context, String text) {
70920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            final List<LabeledIntent> actions = new ArrayList<>();
71005e0051d1d3000525a12f555c59097201487fad8Jan Althaus            final UserManager userManager = context.getSystemService(UserManager.class);
71105e0051d1d3000525a12f555c59097201487fad8Jan Althaus            final Bundle userRestrictions = userManager != null
71205e0051d1d3000525a12f555c59097201487fad8Jan Althaus                    ? userManager.getUserRestrictions() : new Bundle();
71305e0051d1d3000525a12f555c59097201487fad8Jan Althaus            if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
71420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                actions.add(new LabeledIntent(
71520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.dial),
71620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.dial_desc),
71720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        new Intent(Intent.ACTION_DIAL).setData(
718904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                Uri.parse(String.format("tel:%s", text))),
719904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                        LabeledIntent.DEFAULT_REQUEST_CODE));
72005e0051d1d3000525a12f555c59097201487fad8Jan Althaus            }
72120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            actions.add(new LabeledIntent(
72220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.add_contact),
72320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.add_contact_desc),
72420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new Intent(Intent.ACTION_INSERT_OR_EDIT)
72520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
726904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                            .putExtra(ContactsContract.Intents.Insert.PHONE, text),
727904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    text.hashCode()));
72805e0051d1d3000525a12f555c59097201487fad8Jan Althaus            if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
72920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                actions.add(new LabeledIntent(
73020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.sms),
73120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.sms_desc),
73220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        new Intent(Intent.ACTION_SENDTO)
733904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                .setData(Uri.parse(String.format("smsto:%s", text))),
734904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                        LabeledIntent.DEFAULT_REQUEST_CODE));
73505e0051d1d3000525a12f555c59097201487fad8Jan Althaus            }
73620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return actions;
737705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
738705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
739705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
74020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForAddress(Context context, String text) {
74120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            final List<LabeledIntent> actions = new ArrayList<>();
742eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althaus            try {
743eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althaus                final String encText = URLEncoder.encode(text, "UTF-8");
74420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                actions.add(new LabeledIntent(
74520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.map),
74620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        context.getString(com.android.internal.R.string.map_desc),
74720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                        new Intent(Intent.ACTION_VIEW)
748904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))),
749904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                        LabeledIntent.DEFAULT_REQUEST_CODE));
750eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althaus            } catch (UnsupportedEncodingException e) {
751eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althaus                Log.e(LOG_TAG, "Could not encode address", e);
752eaff57ebfe3005fd8a3dc5a1d9085f40f0416b30Jan Althaus            }
75320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return actions;
754705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
755705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
756705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
75720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForUrl(Context context, String text) {
7587dd7c5ceda2c51d378169541aea4d160a4a15587Jan Althaus            if (Uri.parse(text).getScheme() == null) {
7597dd7c5ceda2c51d378169541aea4d160a4a15587Jan Althaus                text = "http://" + text;
760705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            }
76120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return Arrays.asList(new LabeledIntent(
76220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.browse),
76320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.browse_desc),
76420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new Intent(Intent.ACTION_VIEW, Uri.parse(text))
765904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
766904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    LabeledIntent.DEFAULT_REQUEST_CODE));
767705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
768705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
769705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
77020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForDatetime(
771a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                Context context, String type, @Nullable Instant referenceTime,
772a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                Instant parsedTime) {
773705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            if (referenceTime == null) {
774705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus                // If no reference time was given, use now.
775a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                referenceTime = Instant.now();
776705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            }
77720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            List<LabeledIntent> actions = new ArrayList<>();
778a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            actions.add(createCalendarViewIntent(context, parsedTime));
779a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS);
780a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) {
781a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                actions.add(createCalendarCreateEventIntent(context, parsedTime, type));
7826b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki            }
78320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return actions;
7846b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki        }
7856b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki
786705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
78720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static List<LabeledIntent> createForFlight(Context context, String text) {
78820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return Arrays.asList(new LabeledIntent(
78920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.view_flight),
79020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.view_flight_desc),
79120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new Intent(Intent.ACTION_WEB_SEARCH)
792904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                            .putExtra(SearchManager.QUERY, text),
793904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    text.hashCode()));
794705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
795705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
796705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
797a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus        private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) {
798705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
799705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            builder.appendPath("time");
800a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus            ContentUris.appendId(builder, parsedTime.toEpochMilli());
80120d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return new LabeledIntent(
80220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.view_calendar),
80320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.view_calendar_desc),
804904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    new Intent(Intent.ACTION_VIEW).setData(builder.build()),
805904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    LabeledIntent.DEFAULT_REQUEST_CODE);
806705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        }
807705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus
808705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus        @NonNull
80920d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus        private static LabeledIntent createCalendarCreateEventIntent(
810a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                Context context, Instant parsedTime, @EntityType String type) {
811705b9e9a1b435c7b8ab3b47d300ced487ed2262eJan Althaus            final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
81220d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus            return new LabeledIntent(
81320d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.add_calendar_event),
81420d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    context.getString(com.android.internal.R.string.add_calendar_event_desc),
81520d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                    new Intent(Intent.ACTION_INSERT)
81620d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            .setData(CalendarContract.Events.CONTENT_URI)
81720d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
81820d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
819a1652cfcce547183a426cc710691c740b2e46aa7Jan Althaus                                    parsedTime.toEpochMilli())
82020d346eafec9404fb6f5b8eeb9a18ad794b4ca9aJan Althaus                            .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
821904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                                    parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION),
822904a931cfc5f2ffd6fd0c0fb03718abca37b5ee5Abodunrinwa Toki                    parsedTime.hashCode());
8236b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki        }
8246b76675248d442b722e5fbb5c34e5274ced9aeb8Abodunrinwa Toki    }
82543e0350922556e86b641da084a2c9dc2b07fc662Abodunrinwa Toki}
826