blob: 8d60ad850afa2429e5103fe4868cd3e5d7ff3fa9 [file] [log] [blame]
Tony Makfc039c32019-01-17 19:32:08 +00001/*
2 * Copyright (C) 2019 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 */
Tony Mak8ab9b182019-03-01 16:44:17 +000016package android.view.textclassifier.intent;
Tony Makfc039c32019-01-17 19:32:08 +000017
18import static java.time.temporal.ChronoUnit.MILLIS;
19
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.SearchManager;
23import android.content.ContentUris;
24import android.content.Context;
25import android.content.Intent;
26import android.net.Uri;
27import android.os.Bundle;
28import android.os.UserManager;
29import android.provider.Browser;
30import android.provider.CalendarContract;
31import android.provider.ContactsContract;
Tony Mak8ab9b182019-03-01 16:44:17 +000032import android.view.textclassifier.Log;
33import android.view.textclassifier.TextClassifier;
Tony Makfc039c32019-01-17 19:32:08 +000034
35import com.google.android.textclassifier.AnnotatorModel;
36
37import java.io.UnsupportedEncodingException;
38import java.net.URLEncoder;
39import java.time.Instant;
40import java.util.ArrayList;
41import java.util.List;
42import java.util.Locale;
43import java.util.concurrent.TimeUnit;
44
45/**
46 * Creates intents based on the classification type.
47 * @hide
48 */
Tony Mak15b64be2019-04-01 20:02:29 +010049// TODO: Consider to support {@code descriptionWithAppName}.
Tony Mak8ab9b182019-03-01 16:44:17 +000050public final class LegacyClassificationIntentFactory implements ClassificationIntentFactory {
Tony Makfc039c32019-01-17 19:32:08 +000051
Tony Mak8ab9b182019-03-01 16:44:17 +000052 private static final String TAG = "LegacyClassificationIntentFactory";
Tony Makfc039c32019-01-17 19:32:08 +000053 private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5);
54 private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1);
55
Tony Makfc039c32019-01-17 19:32:08 +000056 @NonNull
57 @Override
58 public List<LabeledIntent> create(Context context, String text, boolean foreignText,
59 @Nullable Instant referenceTime,
60 AnnotatorModel.ClassificationResult classification) {
61 final String type = classification != null
62 ? classification.getCollection().trim().toLowerCase(Locale.ENGLISH)
63 : "";
64 text = text.trim();
65 final List<LabeledIntent> actions;
66 switch (type) {
67 case TextClassifier.TYPE_EMAIL:
68 actions = createForEmail(context, text);
69 break;
70 case TextClassifier.TYPE_PHONE:
71 actions = createForPhone(context, text);
72 break;
73 case TextClassifier.TYPE_ADDRESS:
74 actions = createForAddress(context, text);
75 break;
76 case TextClassifier.TYPE_URL:
77 actions = createForUrl(context, text);
78 break;
79 case TextClassifier.TYPE_DATE: // fall through
80 case TextClassifier.TYPE_DATE_TIME:
81 if (classification.getDatetimeResult() != null) {
82 final Instant parsedTime = Instant.ofEpochMilli(
83 classification.getDatetimeResult().getTimeMsUtc());
84 actions = createForDatetime(context, type, referenceTime, parsedTime);
85 } else {
86 actions = new ArrayList<>();
87 }
88 break;
89 case TextClassifier.TYPE_FLIGHT_NUMBER:
90 actions = createForFlight(context, text);
91 break;
92 case TextClassifier.TYPE_DICTIONARY:
93 actions = createForDictionary(context, text);
94 break;
95 default:
96 actions = new ArrayList<>();
97 break;
98 }
99 if (foreignText) {
Tony Mak8ab9b182019-03-01 16:44:17 +0000100 ClassificationIntentFactory.insertTranslateAction(actions, context, text);
Tony Makfc039c32019-01-17 19:32:08 +0000101 }
Tony Makfc039c32019-01-17 19:32:08 +0000102 return actions;
103 }
104
105 @NonNull
106 private static List<LabeledIntent> createForEmail(Context context, String text) {
107 final List<LabeledIntent> actions = new ArrayList<>();
108 actions.add(new LabeledIntent(
109 context.getString(com.android.internal.R.string.email),
Tony Makac9b4d82019-02-15 13:57:38 +0000110 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000111 context.getString(com.android.internal.R.string.email_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100112 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000113 new Intent(Intent.ACTION_SENDTO)
114 .setData(Uri.parse(String.format("mailto:%s", text))),
115 LabeledIntent.DEFAULT_REQUEST_CODE));
116 actions.add(new LabeledIntent(
117 context.getString(com.android.internal.R.string.add_contact),
Tony Makac9b4d82019-02-15 13:57:38 +0000118 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000119 context.getString(com.android.internal.R.string.add_contact_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100120 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000121 new Intent(Intent.ACTION_INSERT_OR_EDIT)
122 .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
123 .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
124 text.hashCode()));
125 return actions;
126 }
127
128 @NonNull
129 private static List<LabeledIntent> createForPhone(Context context, String text) {
130 final List<LabeledIntent> actions = new ArrayList<>();
131 final UserManager userManager = context.getSystemService(UserManager.class);
132 final Bundle userRestrictions = userManager != null
133 ? userManager.getUserRestrictions() : new Bundle();
134 if (!userRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false)) {
135 actions.add(new LabeledIntent(
136 context.getString(com.android.internal.R.string.dial),
Tony Makac9b4d82019-02-15 13:57:38 +0000137 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000138 context.getString(com.android.internal.R.string.dial_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100139 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000140 new Intent(Intent.ACTION_DIAL).setData(
141 Uri.parse(String.format("tel:%s", text))),
142 LabeledIntent.DEFAULT_REQUEST_CODE));
143 }
144 actions.add(new LabeledIntent(
145 context.getString(com.android.internal.R.string.add_contact),
Tony Makac9b4d82019-02-15 13:57:38 +0000146 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000147 context.getString(com.android.internal.R.string.add_contact_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100148 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000149 new Intent(Intent.ACTION_INSERT_OR_EDIT)
150 .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
151 .putExtra(ContactsContract.Intents.Insert.PHONE, text),
152 text.hashCode()));
153 if (!userRestrictions.getBoolean(UserManager.DISALLOW_SMS, false)) {
154 actions.add(new LabeledIntent(
155 context.getString(com.android.internal.R.string.sms),
Tony Makac9b4d82019-02-15 13:57:38 +0000156 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000157 context.getString(com.android.internal.R.string.sms_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100158 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000159 new Intent(Intent.ACTION_SENDTO)
160 .setData(Uri.parse(String.format("smsto:%s", text))),
161 LabeledIntent.DEFAULT_REQUEST_CODE));
162 }
163 return actions;
164 }
165
166 @NonNull
167 private static List<LabeledIntent> createForAddress(Context context, String text) {
168 final List<LabeledIntent> actions = new ArrayList<>();
169 try {
170 final String encText = URLEncoder.encode(text, "UTF-8");
171 actions.add(new LabeledIntent(
172 context.getString(com.android.internal.R.string.map),
Tony Makac9b4d82019-02-15 13:57:38 +0000173 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000174 context.getString(com.android.internal.R.string.map_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100175 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000176 new Intent(Intent.ACTION_VIEW)
177 .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))),
178 LabeledIntent.DEFAULT_REQUEST_CODE));
179 } catch (UnsupportedEncodingException e) {
180 Log.e(TAG, "Could not encode address", e);
181 }
182 return actions;
183 }
184
185 @NonNull
186 private static List<LabeledIntent> createForUrl(Context context, String text) {
187 if (Uri.parse(text).getScheme() == null) {
188 text = "http://" + text;
189 }
190 final List<LabeledIntent> actions = new ArrayList<>();
191 actions.add(new LabeledIntent(
192 context.getString(com.android.internal.R.string.browse),
Tony Makac9b4d82019-02-15 13:57:38 +0000193 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000194 context.getString(com.android.internal.R.string.browse_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100195 /* descriptionWithAppName */ null,
Abodunrinwa Tokic33fc772019-02-06 01:17:10 +0000196 new Intent(Intent.ACTION_VIEW)
197 .setDataAndNormalize(Uri.parse(text))
Tony Makfc039c32019-01-17 19:32:08 +0000198 .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
199 LabeledIntent.DEFAULT_REQUEST_CODE));
200 return actions;
201 }
202
203 @NonNull
204 private static List<LabeledIntent> createForDatetime(
205 Context context, String type, @Nullable Instant referenceTime,
206 Instant parsedTime) {
207 if (referenceTime == null) {
208 // If no reference time was given, use now.
209 referenceTime = Instant.now();
210 }
211 List<LabeledIntent> actions = new ArrayList<>();
212 actions.add(createCalendarViewIntent(context, parsedTime));
213 final long millisUntilEvent = referenceTime.until(parsedTime, MILLIS);
214 if (millisUntilEvent > MIN_EVENT_FUTURE_MILLIS) {
215 actions.add(createCalendarCreateEventIntent(context, parsedTime, type));
216 }
217 return actions;
218 }
219
220 @NonNull
221 private static List<LabeledIntent> createForFlight(Context context, String text) {
222 final List<LabeledIntent> actions = new ArrayList<>();
223 actions.add(new LabeledIntent(
224 context.getString(com.android.internal.R.string.view_flight),
Tony Makac9b4d82019-02-15 13:57:38 +0000225 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000226 context.getString(com.android.internal.R.string.view_flight_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100227 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000228 new Intent(Intent.ACTION_WEB_SEARCH)
229 .putExtra(SearchManager.QUERY, text),
230 text.hashCode()));
231 return actions;
232 }
233
234 @NonNull
235 private static LabeledIntent createCalendarViewIntent(Context context, Instant parsedTime) {
236 Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
237 builder.appendPath("time");
238 ContentUris.appendId(builder, parsedTime.toEpochMilli());
239 return new LabeledIntent(
240 context.getString(com.android.internal.R.string.view_calendar),
Tony Makac9b4d82019-02-15 13:57:38 +0000241 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000242 context.getString(com.android.internal.R.string.view_calendar_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100243 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000244 new Intent(Intent.ACTION_VIEW).setData(builder.build()),
245 LabeledIntent.DEFAULT_REQUEST_CODE);
246 }
247
248 @NonNull
249 private static LabeledIntent createCalendarCreateEventIntent(
250 Context context, Instant parsedTime, @TextClassifier.EntityType String type) {
251 final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
252 return new LabeledIntent(
253 context.getString(com.android.internal.R.string.add_calendar_event),
Tony Makac9b4d82019-02-15 13:57:38 +0000254 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000255 context.getString(com.android.internal.R.string.add_calendar_event_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100256 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000257 new Intent(Intent.ACTION_INSERT)
258 .setData(CalendarContract.Events.CONTENT_URI)
259 .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
260 .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
261 parsedTime.toEpochMilli())
262 .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
263 parsedTime.toEpochMilli() + DEFAULT_EVENT_DURATION),
264 parsedTime.hashCode());
265 }
266
267 @NonNull
268 private static List<LabeledIntent> createForDictionary(Context context, String text) {
269 final List<LabeledIntent> actions = new ArrayList<>();
270 actions.add(new LabeledIntent(
271 context.getString(com.android.internal.R.string.define),
Tony Makac9b4d82019-02-15 13:57:38 +0000272 /* titleWithEntity */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000273 context.getString(com.android.internal.R.string.define_desc),
Tony Mak15b64be2019-04-01 20:02:29 +0100274 /* descriptionWithAppName */ null,
Tony Makfc039c32019-01-17 19:32:08 +0000275 new Intent(Intent.ACTION_DEFINE)
276 .putExtra(Intent.EXTRA_TEXT, text),
277 text.hashCode()));
278 return actions;
279 }
280}