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