Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.view.textclassifier; |
| 18 | |
| 19 | import android.annotation.NonNull; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 20 | import android.annotation.Nullable; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 21 | import android.app.SearchManager; |
Abodunrinwa Toki | fafdb73 | 2017-02-02 11:07:05 +0000 | [diff] [blame] | 22 | import android.content.ComponentName; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 23 | import android.content.ContentUris; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 24 | import android.content.Context; |
| 25 | import android.content.Intent; |
| 26 | import android.content.pm.PackageManager; |
| 27 | import android.content.pm.ResolveInfo; |
| 28 | import android.graphics.drawable.Drawable; |
| 29 | import android.net.Uri; |
Abodunrinwa Toki | 4cfda0b | 2017-02-28 18:56:47 +0000 | [diff] [blame] | 30 | import android.os.LocaleList; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 31 | import android.os.ParcelFileDescriptor; |
Abodunrinwa Toki | 9b4c82a | 2017-02-06 20:29:36 +0000 | [diff] [blame] | 32 | import android.provider.Browser; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 33 | import android.provider.CalendarContract; |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 34 | import android.provider.ContactsContract; |
Abodunrinwa Toki | 0e6b43e | 2017-09-19 23:18:40 +0100 | [diff] [blame] | 35 | import android.provider.Settings; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 36 | import android.text.util.Linkify; |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 37 | import android.util.Patterns; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 38 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 39 | import com.android.internal.annotations.GuardedBy; |
Abodunrinwa Toki | 1d77557 | 2017-05-08 16:03:01 +0100 | [diff] [blame] | 40 | import com.android.internal.logging.MetricsLogger; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 41 | import com.android.internal.util.Preconditions; |
| 42 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 43 | import java.io.File; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 44 | import java.io.FileNotFoundException; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 45 | import java.io.IOException; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 46 | import java.util.ArrayList; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 47 | import java.util.Arrays; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 48 | import java.util.Calendar; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 49 | import java.util.Collection; |
| 50 | import java.util.Collections; |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 51 | import java.util.HashMap; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 52 | import java.util.List; |
Abodunrinwa Toki | 9b4c82a | 2017-02-06 20:29:36 +0000 | [diff] [blame] | 53 | import java.util.Locale; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 54 | import java.util.Map; |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 55 | import java.util.Objects; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 56 | import java.util.concurrent.TimeUnit; |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 57 | import java.util.regex.Matcher; |
| 58 | import java.util.regex.Pattern; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 59 | |
| 60 | /** |
| 61 | * Default implementation of the {@link TextClassifier} interface. |
| 62 | * |
| 63 | * <p>This class uses machine learning to recognize entities in text. |
| 64 | * Unless otherwise stated, methods of this class are blocking operations and should most |
| 65 | * likely not be called on the UI thread. |
| 66 | * |
| 67 | * @hide |
| 68 | */ |
| 69 | final class TextClassifierImpl implements TextClassifier { |
| 70 | |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 71 | private static final String LOG_TAG = DEFAULT_LOG_TAG; |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 72 | private static final String MODEL_DIR = "/etc/textclassifier/"; |
| 73 | private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 74 | private static final String UPDATED_MODEL_FILE_PATH = |
| 75 | "/data/misc/textclassifier/textclassifier.smartselection.model"; |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 76 | private static final List<String> ENTITY_TYPES_ALL = |
| 77 | Collections.unmodifiableList(Arrays.asList( |
| 78 | TextClassifier.TYPE_ADDRESS, |
| 79 | TextClassifier.TYPE_EMAIL, |
| 80 | TextClassifier.TYPE_PHONE, |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 81 | TextClassifier.TYPE_URL, |
| 82 | TextClassifier.TYPE_DATE, |
| 83 | TextClassifier.TYPE_DATE_TIME, |
| 84 | TextClassifier.TYPE_FLIGHT_NUMBER)); |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 85 | private static final List<String> ENTITY_TYPES_BASE = |
| 86 | Collections.unmodifiableList(Arrays.asList( |
| 87 | TextClassifier.TYPE_ADDRESS, |
| 88 | TextClassifier.TYPE_EMAIL, |
| 89 | TextClassifier.TYPE_PHONE, |
| 90 | TextClassifier.TYPE_URL)); |
Abodunrinwa Toki | b89cf02 | 2017-02-06 19:53:22 +0000 | [diff] [blame] | 91 | |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 92 | private final Context mContext; |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 93 | |
Abodunrinwa Toki | 1d77557 | 2017-05-08 16:03:01 +0100 | [diff] [blame] | 94 | private final MetricsLogger mMetricsLogger = new MetricsLogger(); |
| 95 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 96 | private final Object mSmartSelectionLock = new Object(); |
| 97 | @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. |
| 98 | private Map<Locale, String> mModelFilePaths; |
| 99 | @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. |
| 100 | private Locale mLocale; |
| 101 | @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 102 | private int mVersion; |
| 103 | @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 104 | private SmartSelection mSmartSelection; |
| 105 | |
Abodunrinwa Toki | 0e6b43e | 2017-09-19 23:18:40 +0100 | [diff] [blame] | 106 | private TextClassifierConstants mSettings; |
| 107 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 108 | TextClassifierImpl(Context context) { |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 109 | mContext = Preconditions.checkNotNull(context); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | @Override |
| 113 | public TextSelection suggestSelection( |
Abodunrinwa Toki | 4cfda0b | 2017-02-28 18:56:47 +0000 | [diff] [blame] | 114 | @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 115 | @NonNull TextSelection.Options options) { |
| 116 | Utils.validateInput(text, selectionStartIndex, selectionEndIndex); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 117 | try { |
| 118 | if (text.length() > 0) { |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 119 | final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); |
| 120 | final SmartSelection smartSelection = getSmartSelection(locales); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 121 | final String string = text.toString(); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 122 | final int start; |
| 123 | final int end; |
| 124 | if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) { |
| 125 | start = selectionStartIndex; |
| 126 | end = selectionEndIndex; |
| 127 | } else { |
| 128 | final int[] startEnd = smartSelection.suggest( |
| 129 | string, selectionStartIndex, selectionEndIndex); |
| 130 | start = startEnd[0]; |
| 131 | end = startEnd[1]; |
| 132 | } |
Abodunrinwa Toki | b416297 | 2017-05-05 18:07:17 +0100 | [diff] [blame] | 133 | if (start <= end |
| 134 | && start >= 0 && end <= string.length() |
| 135 | && start <= selectionStartIndex && end >= selectionEndIndex) { |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 136 | final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end); |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 137 | final SmartSelection.ClassificationResult[] results = |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 138 | smartSelection.classifyText( |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 139 | string, start, end, |
| 140 | getHintFlags(string, start, end)); |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 141 | final int size = results.length; |
| 142 | for (int i = 0; i < size; i++) { |
| 143 | tsBuilder.setEntityType(results[i].mCollection, results[i].mScore); |
| 144 | } |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 145 | return tsBuilder |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 146 | .setSignature( |
| 147 | getSignature(string, selectionStartIndex, selectionEndIndex)) |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 148 | .build(); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 149 | } else { |
| 150 | // We can not trust the result. Log the issue and ignore the result. |
| 151 | Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result."); |
| 152 | } |
| 153 | } |
| 154 | } catch (Throwable t) { |
| 155 | // Avoid throwing from this method. Log the error. |
| 156 | Log.e(LOG_TAG, |
| 157 | "Error suggesting selection for text. No changes to selection suggested.", |
| 158 | t); |
| 159 | } |
| 160 | // Getting here means something went wrong, return a NO_OP result. |
| 161 | return TextClassifier.NO_OP.suggestSelection( |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 162 | text, selectionStartIndex, selectionEndIndex, options); |
| 163 | } |
| 164 | |
| 165 | @Override |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 166 | public TextClassification classifyText( |
Abodunrinwa Toki | a2df6e5 | 2017-04-13 09:56:52 +0100 | [diff] [blame] | 167 | @NonNull CharSequence text, int startIndex, int endIndex, |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 168 | @NonNull TextClassification.Options options) { |
| 169 | Utils.validateInput(text, startIndex, endIndex); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 170 | try { |
| 171 | if (text.length() > 0) { |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 172 | final String string = text.toString(); |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 173 | final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); |
| 174 | final SmartSelection.ClassificationResult[] results = getSmartSelection(locales) |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 175 | .classifyText(string, startIndex, endIndex, |
| 176 | getHintFlags(string, startIndex, endIndex)); |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 177 | if (results.length > 0) { |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 178 | return createClassificationResult( |
| 179 | results, string, startIndex, endIndex, options.getReferenceTime()); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 180 | } |
| 181 | } |
| 182 | } catch (Throwable t) { |
| 183 | // Avoid throwing from this method. Log the error. |
Abodunrinwa Toki | 7861885 | 2017-10-17 15:31:39 +0100 | [diff] [blame] | 184 | Log.e(LOG_TAG, "Error getting text classification info.", t); |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 185 | } |
| 186 | // Getting here means something went wrong, return a NO_OP result. |
Abodunrinwa Toki | 2b6020f | 2017-10-28 02:28:45 +0100 | [diff] [blame] | 187 | return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options); |
| 188 | } |
| 189 | |
| 190 | @Override |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 191 | public TextLinks generateLinks( |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 192 | @NonNull CharSequence text, @Nullable TextLinks.Options options) { |
Abodunrinwa Toki | 4d232d6 | 2017-11-23 12:22:45 +0000 | [diff] [blame] | 193 | Utils.validateInput(text); |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 194 | final String textString = text.toString(); |
| 195 | final TextLinks.Builder builder = new TextLinks.Builder(textString); |
Richard Ledley | 9cfa606 | 2018-01-15 13:13:29 +0000 | [diff] [blame] | 196 | |
| 197 | if (!getSettings().isSmartLinkifyEnabled()) { |
| 198 | return builder.build(); |
| 199 | } |
| 200 | |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 201 | try { |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 202 | final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; |
| 203 | final Collection<String> entitiesToIdentify = |
| 204 | options != null && options.getEntityConfig() != null |
| 205 | ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL; |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 206 | final SmartSelection smartSelection = getSmartSelection(defaultLocales); |
| 207 | final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString); |
| 208 | for (SmartSelection.AnnotatedSpan span : annotations) { |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 209 | final SmartSelection.ClassificationResult[] results = span.getClassification(); |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 210 | if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) { |
| 211 | continue; |
| 212 | } |
| 213 | final Map<String, Float> entityScores = new HashMap<>(); |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 214 | for (int i = 0; i < results.length; i++) { |
| 215 | entityScores.put(results[i].mCollection, results[i].mScore); |
| 216 | } |
| 217 | builder.addLink(new TextLinks.TextLink( |
| 218 | textString, span.getStartIndex(), span.getEndIndex(), entityScores)); |
| 219 | } |
| 220 | } catch (Throwable t) { |
| 221 | // Avoid throwing from this method. Log the error. |
| 222 | Log.e(LOG_TAG, "Error getting links info.", t); |
| 223 | } |
| 224 | return builder.build(); |
| 225 | } |
| 226 | |
| 227 | @Override |
Richard Ledley | db18a57 | 2017-11-30 17:33:51 +0000 | [diff] [blame] | 228 | public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) { |
| 229 | switch (entityPreset) { |
| 230 | case TextClassifier.ENTITY_PRESET_NONE: |
| 231 | return Collections.emptyList(); |
| 232 | case TextClassifier.ENTITY_PRESET_BASE: |
| 233 | return ENTITY_TYPES_BASE; |
| 234 | case TextClassifier.ENTITY_PRESET_ALL: |
| 235 | // fall through |
| 236 | default: |
| 237 | return ENTITY_TYPES_ALL; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | @Override |
Abodunrinwa Toki | 1d77557 | 2017-05-08 16:03:01 +0100 | [diff] [blame] | 242 | public void logEvent(String source, String event) { |
| 243 | if (LOG_TAG.equals(source)) { |
| 244 | mMetricsLogger.count(event, 1); |
| 245 | } |
| 246 | } |
| 247 | |
Abodunrinwa Toki | 0e6b43e | 2017-09-19 23:18:40 +0100 | [diff] [blame] | 248 | @Override |
| 249 | public TextClassifierConstants getSettings() { |
| 250 | if (mSettings == null) { |
| 251 | mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString( |
| 252 | mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); |
| 253 | } |
| 254 | return mSettings; |
| 255 | } |
| 256 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 257 | private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { |
Abodunrinwa Toki | b89cf02 | 2017-02-06 19:53:22 +0000 | [diff] [blame] | 258 | synchronized (mSmartSelectionLock) { |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 259 | localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; |
| 260 | final Locale locale = findBestSupportedLocaleLocked(localeList); |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 261 | if (locale == null) { |
| 262 | throw new FileNotFoundException("No file for null locale"); |
| 263 | } |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 264 | if (mSmartSelection == null || !Objects.equals(mLocale, locale)) { |
| 265 | destroySmartSelectionIfExistsLocked(); |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 266 | final ParcelFileDescriptor fd = getFdLocked(locale); |
Jan Althaus | e750c6c | 2017-11-13 12:07:04 +0100 | [diff] [blame] | 267 | final int modelFd = fd.getFd(); |
| 268 | mVersion = SmartSelection.getVersion(modelFd); |
| 269 | mSmartSelection = new SmartSelection(modelFd); |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 270 | closeAndLogError(fd); |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 271 | mLocale = locale; |
Abodunrinwa Toki | b89cf02 | 2017-02-06 19:53:22 +0000 | [diff] [blame] | 272 | } |
| 273 | return mSmartSelection; |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 274 | } |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 275 | } |
| 276 | |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 277 | private String getSignature(String text, int start, int end) { |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 278 | synchronized (mSmartSelectionLock) { |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 279 | final String versionInfo = (mLocale != null) |
| 280 | ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion) |
| 281 | : ""; |
| 282 | final int hash = Objects.hash(text, start, end, mContext.getPackageName()); |
| 283 | return String.format(Locale.US, "%s|%s|%d", LOG_TAG, versionInfo, hash); |
Abodunrinwa Toki | 692b196 | 2017-08-15 15:05:11 +0100 | [diff] [blame] | 284 | } |
| 285 | } |
| 286 | |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 287 | @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 288 | private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException { |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 289 | ParcelFileDescriptor updateFd; |
Jan Althaus | e750c6c | 2017-11-13 12:07:04 +0100 | [diff] [blame] | 290 | int updateVersion = -1; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 291 | try { |
| 292 | updateFd = ParcelFileDescriptor.open( |
| 293 | new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY); |
Jan Althaus | e750c6c | 2017-11-13 12:07:04 +0100 | [diff] [blame] | 294 | if (updateFd != null) { |
| 295 | updateVersion = SmartSelection.getVersion(updateFd.getFd()); |
| 296 | } |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 297 | } catch (FileNotFoundException e) { |
| 298 | updateFd = null; |
| 299 | } |
| 300 | ParcelFileDescriptor factoryFd; |
Jan Althaus | e750c6c | 2017-11-13 12:07:04 +0100 | [diff] [blame] | 301 | int factoryVersion = -1; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 302 | try { |
| 303 | final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale); |
| 304 | if (factoryModelFilePath != null) { |
| 305 | factoryFd = ParcelFileDescriptor.open( |
| 306 | new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY); |
Jan Althaus | e750c6c | 2017-11-13 12:07:04 +0100 | [diff] [blame] | 307 | if (factoryFd != null) { |
| 308 | factoryVersion = SmartSelection.getVersion(factoryFd.getFd()); |
| 309 | } |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 310 | } else { |
| 311 | factoryFd = null; |
| 312 | } |
| 313 | } catch (FileNotFoundException e) { |
| 314 | factoryFd = null; |
| 315 | } |
| 316 | |
| 317 | if (updateFd == null) { |
| 318 | if (factoryFd != null) { |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 319 | return factoryFd; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 320 | } else { |
| 321 | throw new FileNotFoundException( |
| 322 | String.format("No model file found for %s", locale)); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | final int updateFdInt = updateFd.getFd(); |
| 327 | final boolean localeMatches = Objects.equals( |
| 328 | locale.getLanguage().trim().toLowerCase(), |
| 329 | SmartSelection.getLanguage(updateFdInt).trim().toLowerCase()); |
| 330 | if (factoryFd == null) { |
| 331 | if (localeMatches) { |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 332 | return updateFd; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 333 | } else { |
| 334 | closeAndLogError(updateFd); |
| 335 | throw new FileNotFoundException( |
| 336 | String.format("No model file found for %s", locale)); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | if (!localeMatches) { |
| 341 | closeAndLogError(updateFd); |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 342 | return factoryFd; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 343 | } |
| 344 | |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 345 | if (updateVersion > factoryVersion) { |
| 346 | closeAndLogError(factoryFd); |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 347 | return updateFd; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 348 | } else { |
| 349 | closeAndLogError(updateFd); |
Abodunrinwa Toki | 6ace893 | 2017-04-28 19:25:24 +0100 | [diff] [blame] | 350 | return factoryFd; |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 351 | } |
| 352 | } |
| 353 | |
| 354 | @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 355 | private void destroySmartSelectionIfExistsLocked() { |
| 356 | if (mSmartSelection != null) { |
| 357 | mSmartSelection.close(); |
| 358 | mSmartSelection = null; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. |
| 363 | @Nullable |
| 364 | private Locale findBestSupportedLocaleLocked(LocaleList localeList) { |
Abodunrinwa Toki | a2df6e5 | 2017-04-13 09:56:52 +0100 | [diff] [blame] | 365 | // Specified localeList takes priority over the system default, so it is listed first. |
| 366 | final String languages = localeList.isEmpty() |
| 367 | ? LocaleList.getDefault().toLanguageTags() |
| 368 | : localeList.toLanguageTags() + "," + LocaleList.getDefault().toLanguageTags(); |
| 369 | final List<Locale.LanguageRange> languageRangeList = Locale.LanguageRange.parse(languages); |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 370 | |
| 371 | final List<Locale> supportedLocales = |
| 372 | new ArrayList<>(getFactoryModelFilePathsLocked().keySet()); |
| 373 | final Locale updatedModelLocale = getUpdatedModelLocale(); |
| 374 | if (updatedModelLocale != null) { |
| 375 | supportedLocales.add(updatedModelLocale); |
| 376 | } |
| 377 | return Locale.lookup(languageRangeList, supportedLocales); |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 378 | } |
| 379 | |
| 380 | @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 381 | private Map<Locale, String> getFactoryModelFilePathsLocked() { |
Abodunrinwa Toki | c39006a1 | 2017-03-29 01:25:23 +0100 | [diff] [blame] | 382 | if (mModelFilePaths == null) { |
| 383 | final Map<Locale, String> modelFilePaths = new HashMap<>(); |
| 384 | final File modelsDir = new File(MODEL_DIR); |
| 385 | if (modelsDir.exists() && modelsDir.isDirectory()) { |
| 386 | final File[] models = modelsDir.listFiles(); |
| 387 | final Pattern modelFilenamePattern = Pattern.compile(MODEL_FILE_REGEX); |
| 388 | final int size = models.length; |
| 389 | for (int i = 0; i < size; i++) { |
| 390 | final File modelFile = models[i]; |
| 391 | final Matcher matcher = modelFilenamePattern.matcher(modelFile.getName()); |
| 392 | if (matcher.matches() && modelFile.isFile()) { |
| 393 | final String language = matcher.group(1); |
| 394 | final Locale locale = Locale.forLanguageTag(language); |
| 395 | modelFilePaths.put(locale, modelFile.getAbsolutePath()); |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | mModelFilePaths = modelFilePaths; |
| 400 | } |
| 401 | return mModelFilePaths; |
| 402 | } |
| 403 | |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 404 | @Nullable |
| 405 | private Locale getUpdatedModelLocale() { |
| 406 | try { |
| 407 | final ParcelFileDescriptor updateFd = ParcelFileDescriptor.open( |
| 408 | new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY); |
| 409 | final Locale locale = Locale.forLanguageTag( |
| 410 | SmartSelection.getLanguage(updateFd.getFd())); |
| 411 | closeAndLogError(updateFd); |
| 412 | return locale; |
| 413 | } catch (FileNotFoundException e) { |
| 414 | return null; |
| 415 | } |
| 416 | } |
| 417 | |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 418 | private TextClassification createClassificationResult( |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 419 | SmartSelection.ClassificationResult[] classifications, |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 420 | String text, int start, int end, @Nullable Calendar referenceTime) { |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 421 | final String classifiedText = text.substring(start, end); |
Abodunrinwa Toki | e0b5789 | 2017-04-28 19:59:57 +0100 | [diff] [blame] | 422 | final TextClassification.Builder builder = new TextClassification.Builder() |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 423 | .setText(classifiedText); |
Abodunrinwa Toki | 9b4c82a | 2017-02-06 20:29:36 +0000 | [diff] [blame] | 424 | |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 425 | final int size = classifications.length; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 426 | SmartSelection.ClassificationResult highestScoringResult = null; |
| 427 | float highestScore = Float.MIN_VALUE; |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 428 | for (int i = 0; i < size; i++) { |
| 429 | builder.setEntityType(classifications[i].mCollection, classifications[i].mScore); |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 430 | if (classifications[i].mScore > highestScore) { |
| 431 | highestScoringResult = classifications[i]; |
| 432 | highestScore = classifications[i].mScore; |
| 433 | } |
Abodunrinwa Toki | a6096f6 | 2017-03-08 17:21:40 +0000 | [diff] [blame] | 434 | } |
| 435 | |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 436 | addActions(builder, IntentFactory.create( |
| 437 | mContext, referenceTime, highestScoringResult, classifiedText)); |
Abodunrinwa Toki | 54486c1 | 2017-04-19 21:02:36 +0100 | [diff] [blame] | 438 | |
Abodunrinwa Toki | 008f387 | 2017-11-27 19:32:35 +0000 | [diff] [blame] | 439 | return builder.setSignature(getSignature(text, start, end)).build(); |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 440 | } |
| 441 | |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 442 | /** Extends the classification with the intents that can be resolved. */ |
| 443 | private void addActions( |
| 444 | TextClassification.Builder builder, List<Intent> intents) { |
| 445 | final PackageManager pm = mContext.getPackageManager(); |
| 446 | final int size = intents.size(); |
| 447 | for (int i = 0; i < size; i++) { |
| 448 | final Intent intent = intents.get(i); |
| 449 | final ResolveInfo resolveInfo; |
| 450 | if (intent != null) { |
| 451 | resolveInfo = pm.resolveActivity(intent, 0); |
Abodunrinwa Toki | fafdb73 | 2017-02-02 11:07:05 +0000 | [diff] [blame] | 452 | } else { |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 453 | resolveInfo = null; |
| 454 | } |
| 455 | if (resolveInfo != null && resolveInfo.activityInfo != null) { |
| 456 | final String packageName = resolveInfo.activityInfo.packageName; |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 457 | final String label = IntentFactory.getLabel(mContext, intent); |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 458 | Drawable icon; |
| 459 | if ("android".equals(packageName)) { |
| 460 | // Requires the chooser to find an activity to handle the intent. |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 461 | icon = null; |
| 462 | } else { |
| 463 | // A default activity will handle the intent. |
| 464 | intent.setComponent( |
| 465 | new ComponentName(packageName, resolveInfo.activityInfo.name)); |
| 466 | icon = resolveInfo.activityInfo.loadIcon(pm); |
| 467 | if (icon == null) { |
| 468 | icon = resolveInfo.loadIcon(pm); |
| 469 | } |
Abodunrinwa Toki | fafdb73 | 2017-02-02 11:07:05 +0000 | [diff] [blame] | 470 | } |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 471 | if (i == 0) { |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 472 | builder.setPrimaryAction(intent, label, icon); |
Abodunrinwa Toki | ba38562 | 2017-11-29 19:30:32 +0000 | [diff] [blame] | 473 | } else { |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 474 | builder.addSecondaryAction(intent, label, icon); |
Abodunrinwa Toki | fafdb73 | 2017-02-02 11:07:05 +0000 | [diff] [blame] | 475 | } |
Abodunrinwa Toki | fafdb73 | 2017-02-02 11:07:05 +0000 | [diff] [blame] | 476 | } |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 477 | } |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 478 | } |
| 479 | |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 480 | private static int getHintFlags(CharSequence text, int start, int end) { |
| 481 | int flag = 0; |
| 482 | final CharSequence subText = text.subSequence(start, end); |
| 483 | if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) { |
| 484 | flag |= SmartSelection.HINT_FLAG_EMAIL; |
| 485 | } |
| 486 | if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches() |
| 487 | && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) { |
| 488 | flag |= SmartSelection.HINT_FLAG_URL; |
| 489 | } |
Abodunrinwa Toki | d2d1399 | 2017-03-24 21:43:13 +0000 | [diff] [blame] | 490 | return flag; |
| 491 | } |
| 492 | |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 493 | /** |
Abodunrinwa Toki | 146d0d4 | 2017-04-25 01:39:19 +0100 | [diff] [blame] | 494 | * Closes the ParcelFileDescriptor and logs any errors that occur. |
| 495 | */ |
| 496 | private static void closeAndLogError(ParcelFileDescriptor fd) { |
| 497 | try { |
| 498 | fd.close(); |
| 499 | } catch (IOException e) { |
| 500 | Log.e(LOG_TAG, "Error closing file.", e); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | /** |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 505 | * Creates intents based on the classification type. |
| 506 | */ |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 507 | static final class IntentFactory { |
| 508 | |
| 509 | private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5); |
| 510 | private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1); |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 511 | |
| 512 | private IntentFactory() {} |
| 513 | |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 514 | @NonNull |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 515 | public static List<Intent> create( |
| 516 | Context context, |
| 517 | @Nullable Calendar referenceTime, |
| 518 | SmartSelection.ClassificationResult classification, |
| 519 | String text) { |
| 520 | final String type = classification.mCollection.trim().toLowerCase(Locale.ENGLISH); |
Abodunrinwa Toki | 70d41cd | 2017-05-02 21:43:41 +0100 | [diff] [blame] | 521 | text = text.trim(); |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 522 | switch (type) { |
| 523 | case TextClassifier.TYPE_EMAIL: |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 524 | return createForEmail(text); |
| 525 | case TextClassifier.TYPE_PHONE: |
| 526 | return createForPhone(text); |
| 527 | case TextClassifier.TYPE_ADDRESS: |
| 528 | return createForAddress(text); |
| 529 | case TextClassifier.TYPE_URL: |
| 530 | return createForUrl(context, text); |
| 531 | case TextClassifier.TYPE_DATE: |
| 532 | case TextClassifier.TYPE_DATE_TIME: |
| 533 | if (classification.mDatetime != null) { |
| 534 | Calendar eventTime = Calendar.getInstance(); |
| 535 | eventTime.setTimeInMillis(classification.mDatetime.mMsSinceEpoch); |
| 536 | return createForDatetime(type, referenceTime, eventTime); |
| 537 | } else { |
| 538 | return new ArrayList<>(); |
| 539 | } |
| 540 | case TextClassifier.TYPE_FLIGHT_NUMBER: |
| 541 | return createForFlight(text); |
| 542 | default: |
| 543 | return new ArrayList<>(); |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | @NonNull |
| 548 | private static List<Intent> createForEmail(String text) { |
| 549 | return Arrays.asList( |
| 550 | new Intent(Intent.ACTION_SENDTO) |
| 551 | .setData(Uri.parse(String.format("mailto:%s", text))), |
| 552 | new Intent(Intent.ACTION_INSERT_OR_EDIT) |
Richard Ledley | 68d9452 | 2017-10-05 10:52:19 +0100 | [diff] [blame] | 553 | .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) |
| 554 | .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 555 | } |
| 556 | |
| 557 | @NonNull |
| 558 | private static List<Intent> createForPhone(String text) { |
| 559 | return Arrays.asList( |
| 560 | new Intent(Intent.ACTION_DIAL) |
| 561 | .setData(Uri.parse(String.format("tel:%s", text))), |
| 562 | new Intent(Intent.ACTION_INSERT_OR_EDIT) |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 563 | .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 564 | .putExtra(ContactsContract.Intents.Insert.PHONE, text), |
| 565 | new Intent(Intent.ACTION_SENDTO) |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 566 | .setData(Uri.parse(String.format("smsto:%s", text)))); |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 567 | } |
| 568 | |
| 569 | @NonNull |
| 570 | private static List<Intent> createForAddress(String text) { |
| 571 | return Arrays.asList(new Intent(Intent.ACTION_VIEW) |
| 572 | .setData(Uri.parse(String.format("geo:0,0?q=%s", text)))); |
| 573 | } |
| 574 | |
| 575 | @NonNull |
| 576 | private static List<Intent> createForUrl(Context context, String text) { |
| 577 | final String httpPrefix = "http://"; |
| 578 | final String httpsPrefix = "https://"; |
| 579 | if (text.toLowerCase().startsWith(httpPrefix)) { |
| 580 | text = httpPrefix + text.substring(httpPrefix.length()); |
| 581 | } else if (text.toLowerCase().startsWith(httpsPrefix)) { |
| 582 | text = httpsPrefix + text.substring(httpsPrefix.length()); |
| 583 | } else { |
| 584 | text = httpPrefix + text; |
| 585 | } |
| 586 | return Arrays.asList(new Intent(Intent.ACTION_VIEW, Uri.parse(text)) |
| 587 | .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())); |
| 588 | } |
| 589 | |
| 590 | @NonNull |
| 591 | private static List<Intent> createForDatetime( |
| 592 | String type, @Nullable Calendar referenceTime, Calendar eventTime) { |
| 593 | if (referenceTime == null) { |
| 594 | // If no reference time was given, use now. |
| 595 | referenceTime = Calendar.getInstance(); |
| 596 | } |
| 597 | List<Intent> intents = new ArrayList<>(); |
| 598 | intents.add(createCalendarViewIntent(eventTime)); |
| 599 | final long millisSinceReference = |
| 600 | eventTime.getTimeInMillis() - referenceTime.getTimeInMillis(); |
| 601 | if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) { |
| 602 | intents.add(createCalendarCreateEventIntent(eventTime, type)); |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 603 | } |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 604 | return intents; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 605 | } |
| 606 | |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 607 | @NonNull |
| 608 | private static List<Intent> createForFlight(String text) { |
| 609 | return Arrays.asList(new Intent(Intent.ACTION_WEB_SEARCH) |
| 610 | .putExtra(SearchManager.QUERY, text)); |
| 611 | } |
| 612 | |
| 613 | @NonNull |
| 614 | private static Intent createCalendarViewIntent(Calendar eventTime) { |
| 615 | Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); |
| 616 | builder.appendPath("time"); |
| 617 | ContentUris.appendId(builder, eventTime.getTimeInMillis()); |
| 618 | return new Intent(Intent.ACTION_VIEW).setData(builder.build()); |
| 619 | } |
| 620 | |
| 621 | @NonNull |
| 622 | private static Intent createCalendarCreateEventIntent( |
| 623 | Calendar eventTime, @EntityType String type) { |
| 624 | final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type); |
| 625 | return new Intent(Intent.ACTION_INSERT) |
| 626 | .setData(CalendarContract.Events.CONTENT_URI) |
| 627 | .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) |
| 628 | .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, eventTime.getTimeInMillis()) |
| 629 | .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, |
| 630 | eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION); |
| 631 | } |
| 632 | |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 633 | @Nullable |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 634 | public static String getLabel(Context context, @Nullable Intent intent) { |
| 635 | if (intent == null || intent.getAction() == null) { |
| 636 | return null; |
| 637 | } |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 638 | final String authority = |
| 639 | intent.getData() == null ? null : intent.getData().getAuthority(); |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 640 | switch (intent.getAction()) { |
| 641 | case Intent.ACTION_DIAL: |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 642 | return context.getString(com.android.internal.R.string.dial); |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 643 | case Intent.ACTION_SENDTO: |
| 644 | switch (intent.getScheme()) { |
| 645 | case "mailto": |
| 646 | return context.getString(com.android.internal.R.string.email); |
| 647 | case "smsto": |
| 648 | return context.getString(com.android.internal.R.string.sms); |
| 649 | default: |
| 650 | return null; |
| 651 | } |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 652 | case Intent.ACTION_INSERT: |
| 653 | if (CalendarContract.AUTHORITY.equals(authority)) { |
| 654 | return context.getString(com.android.internal.R.string.add_calendar_event); |
| 655 | } |
| 656 | return null; |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 657 | case Intent.ACTION_INSERT_OR_EDIT: |
| 658 | switch (intent.getDataString()) { |
| 659 | case ContactsContract.Contacts.CONTENT_ITEM_TYPE: |
| 660 | return context.getString(com.android.internal.R.string.add_contact); |
| 661 | default: |
| 662 | return null; |
| 663 | } |
| 664 | case Intent.ACTION_VIEW: |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 665 | if (CalendarContract.AUTHORITY.equals(authority)) { |
| 666 | return context.getString(com.android.internal.R.string.view_calendar); |
| 667 | } |
Jan Althaus | 92d7683 | 2017-09-27 18:14:35 +0200 | [diff] [blame] | 668 | switch (intent.getScheme()) { |
| 669 | case "geo": |
| 670 | return context.getString(com.android.internal.R.string.map); |
| 671 | case "http": // fall through |
| 672 | case "https": |
| 673 | return context.getString(com.android.internal.R.string.browse); |
| 674 | default: |
| 675 | return null; |
| 676 | } |
Jan Althaus | 705b9e9 | 2018-01-22 18:22:29 +0100 | [diff] [blame] | 677 | case Intent.ACTION_WEB_SEARCH: |
| 678 | return context.getString(com.android.internal.R.string.view_flight); |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 679 | default: |
| 680 | return null; |
Abodunrinwa Toki | 6b76675 | 2017-01-17 16:25:38 -0800 | [diff] [blame] | 681 | } |
| 682 | } |
| 683 | } |
Abodunrinwa Toki | 43e0350 | 2017-01-13 13:46:33 -0800 | [diff] [blame] | 684 | } |